commit
557189f41b
37
.github/workflows/job_release.yml
vendored
37
.github/workflows/job_release.yml
vendored
|
@ -14,39 +14,34 @@ on:
|
|||
type: string
|
||||
name2:
|
||||
type: string
|
||||
default: ""
|
||||
file2:
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Upload
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- name: ${{ inputs.name1 }}
|
||||
file: ${{ inputs.file1 }}
|
||||
- name: ${{ inputs.name2 }}
|
||||
file: ${{ inputs.file2 }}
|
||||
steps:
|
||||
|
||||
- name: Download ${{ inputs.name1 }} artifact
|
||||
- name: Download ${{ matrix.name }} from artifact
|
||||
uses: actions/download-artifact@v3
|
||||
if: ${{ matrix.name != '' }}
|
||||
with:
|
||||
name: ${{ inputs.name1 }}
|
||||
- name: Upload ${{ inputs.name1 }} to release
|
||||
name: ${{ matrix.name }}
|
||||
- name: Upload ${{ matrix.name }} to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
if: ${{ matrix.name != '' }}
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ inputs.file1 }}
|
||||
asset_name: ${{ inputs.name1 }}
|
||||
file: ${{ matrix.file }}
|
||||
asset_name: ${{ matrix.name }}
|
||||
tag: ${{ inputs.version }}
|
||||
overwrite: true
|
||||
|
||||
- name: Download ${{ inputs.name2 }} artifact
|
||||
uses: actions/download-artifact@v3
|
||||
if: ${{ inputs.name2 != '' }}
|
||||
with:
|
||||
name: ${{ inputs.name2 }}
|
||||
- name: Upload ${{ inputs.name2 }} to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
if: ${{ inputs.name2 != '' }}
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ${{ inputs.file2 }}
|
||||
asset_name: ${{ inputs.name2 }}
|
||||
tag: ${{ inputs.version }}
|
||||
overwrite: true
|
|
@ -2,9 +2,9 @@
|
|||
host = https://www.transifex.com
|
||||
|
||||
[o:rare-1:p:rare:r:placeholder-ts]
|
||||
file_filter = rare/resources/languages/<lang>.ts
|
||||
source_file = rare/resources/languages/translation_source.ts
|
||||
source_lang = en_US
|
||||
file_filter = rare/resources/languages/rare_<lang>.ts
|
||||
source_file = rare/resources/languages/source.ts
|
||||
source_lang = en
|
||||
type = QT
|
||||
minimum_perc = 50
|
||||
|
||||
|
|
|
@ -2,4 +2,4 @@ import pkg_resources
|
|||
from subprocess import call
|
||||
|
||||
for dist in pkg_resources.working_set:
|
||||
call("python -m pip install --upgrade " + dist.project_name, shell=True)
|
||||
call(f"python -m pip install --upgrade {dist.project_name}", shell=True)
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
cwd="$(pwd)"
|
||||
cd "$(dirname "$0")"/.. || exit
|
||||
|
||||
pylupdate5 -noobsolete $(find rare/ -iname "*.py") -ts rare/resources/languages/translation_source.ts
|
||||
#py_files=$(find rare -iname "*.py" -not -path rare/ui)
|
||||
#ui_files=$(find rare/ui -iname "*.ui")
|
||||
|
||||
pylupdate5 -noobsolete $(find rare/ -iname "*.py") -ts rare/resources/languages/source.ts
|
||||
|
||||
cd "$cwd" || exit
|
||||
|
|
|
@ -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
|
||||
|
@ -142,11 +143,11 @@ class RareLauncher(RareApp):
|
|||
return
|
||||
self.rgame = RareGameSlim(self.core, game)
|
||||
|
||||
lang = self.settings.value("language", self.core.language_code, type=str)
|
||||
self.load_translator(lang)
|
||||
language = self.settings.value(*options.language)
|
||||
self.load_translator(language)
|
||||
|
||||
if QSettings().value("show_console", False, bool):
|
||||
self.console = ConsoleDialog()
|
||||
if QSettings(self).value(*options.log_games):
|
||||
self.console = ConsoleDialog(game.app_title)
|
||||
self.console.show()
|
||||
|
||||
self.game_process.finished.connect(self.__process_finished)
|
||||
|
@ -294,10 +295,10 @@ class RareLauncher(RareApp):
|
|||
))
|
||||
if self.rgame.app_name in DETACHED_APP_NAMES and platform.system() == "Windows":
|
||||
self.game_process.deleteLater()
|
||||
subprocess.Popen([args.executable] + args.arguments, cwd=args.working_directory,
|
||||
env={i: args.environment.value(i) for i in args.environment.keys()})
|
||||
if self.console:
|
||||
self.console.log("Launching game as a detached process")
|
||||
subprocess.Popen([args.executable] + args.arguments, cwd=args.working_directory,
|
||||
env={i: args.environment.value(i) for i in args.environment.keys()})
|
||||
self.stop()
|
||||
return
|
||||
if self.args.dry_run:
|
|
@ -10,7 +10,7 @@ from legendary.models.game import InstalledGame
|
|||
|
||||
from rare.ui.components.tabs.games.game_info.cloud_sync_widget import Ui_CloudSyncWidget
|
||||
from rare.utils.misc import icon
|
||||
from rare.widgets.dialogs import ButtonDialog, dialog_title_game
|
||||
from rare.widgets.dialogs import ButtonDialog, game_title
|
||||
|
||||
logger = getLogger("CloudSyncDialog")
|
||||
|
||||
|
@ -28,9 +28,9 @@ class CloudSyncDialog(ButtonDialog):
|
|||
def __init__(self, igame: InstalledGame, dt_local: datetime, dt_remote: datetime, parent=None):
|
||||
super(CloudSyncDialog, self).__init__(parent=parent)
|
||||
header = self.tr("Cloud saves for")
|
||||
self.setWindowTitle(dialog_title_game(header, igame.title))
|
||||
self.setWindowTitle(game_title(header, igame.title))
|
||||
|
||||
title_label = QLabel(f"<h4>{dialog_title_game(header, igame.title)}</h4>", self)
|
||||
title_label = QLabel(f"<h4>{game_title(header, igame.title)}</h4>", self)
|
||||
|
||||
sync_widget = QWidget(self)
|
||||
self.sync_ui = Ui_CloudSyncWidget()
|
|
@ -1,4 +1,3 @@
|
|||
import platform
|
||||
from typing import Union
|
||||
|
||||
from PyQt5.QtCore import QProcessEnvironment, pyqtSignal, QSize, Qt
|
||||
|
@ -15,6 +14,7 @@ from PyQt5.QtWidgets import (
|
|||
)
|
||||
|
||||
from rare.ui.launcher.console_env import Ui_ConsoleEnv
|
||||
from rare.widgets.dialogs import dialog_title, game_title
|
||||
|
||||
|
||||
class ConsoleDialog(QDialog):
|
||||
|
@ -22,10 +22,12 @@ class ConsoleDialog(QDialog):
|
|||
kill = pyqtSignal()
|
||||
env: QProcessEnvironment
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, app_title: str, parent=None):
|
||||
super(ConsoleDialog, self).__init__(parent=parent)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose, True)
|
||||
self.setWindowTitle("Rare - Console")
|
||||
self.setWindowTitle(
|
||||
dialog_title(game_title(self.tr("Console"), app_title))
|
||||
)
|
||||
self.setGeometry(0, 0, 640, 480)
|
||||
layout = QVBoxLayout()
|
||||
|
||||
|
@ -62,7 +64,7 @@ class ConsoleDialog(QDialog):
|
|||
|
||||
self.setLayout(layout)
|
||||
|
||||
self.env_variables = ConsoleEnv(self)
|
||||
self.env_variables = ConsoleEnv(app_title, self)
|
||||
self.env_variables.hide()
|
||||
|
||||
self.accept_close = False
|
||||
|
@ -140,11 +142,14 @@ class ConsoleDialog(QDialog):
|
|||
|
||||
class ConsoleEnv(QDialog):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, app_title: str, parent=None):
|
||||
super(ConsoleEnv, self).__init__(parent=parent)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose, False)
|
||||
self.ui = Ui_ConsoleEnv()
|
||||
self.ui.setupUi(self)
|
||||
self.setWindowTitle(
|
||||
dialog_title(game_title(self.tr("Environment"), app_title))
|
||||
)
|
||||
|
||||
def setTable(self, env: QProcessEnvironment):
|
||||
self.ui.table.clearContents()
|
||||
|
@ -168,12 +173,12 @@ class ConsoleEdit(QPlainTextEdit):
|
|||
self._cursor_output = self.textCursor()
|
||||
|
||||
def log(self, text):
|
||||
html = f"<p style=\"color:#BBB;white-space:pre\">{text}</p>"
|
||||
html = f"<p style=\"color:#aaa;white-space:pre\">{text}</p>"
|
||||
self._cursor_output.insertHtml(html)
|
||||
self.scroll_to_last_line()
|
||||
|
||||
def error(self, text):
|
||||
html = f"<p style=\"color:#eee;white-space:pre\">{text}</p>"
|
||||
html = f"<p style=\"color:#a33;white-space:pre\">{text}</p>"
|
||||
self._cursor_output.insertHtml(html)
|
||||
self.scroll_to_last_line()
|
||||
|
0
rare/commands/reaper.py
Normal file
0
rare/commands/reaper.py
Normal file
13
rare/commands/webview.py
Normal file
13
rare/commands/webview.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
import sys
|
||||
from argparse import Namespace
|
||||
|
||||
from legendary.utils import webview_login
|
||||
|
||||
|
||||
def launch(args: Namespace) -> int:
|
||||
if webview_login.do_webview_login(
|
||||
callback_code=sys.stdout.write, user_agent=f'EpicGamesLauncher/{args.egl_version}'
|
||||
):
|
||||
return 0
|
||||
else:
|
||||
return 1
|
|
@ -9,6 +9,7 @@ from PyQt5.QtCore import QThreadPool, QTimer, pyqtSlot, Qt
|
|||
from PyQt5.QtWidgets import QApplication, QMessageBox
|
||||
from requests import HTTPError
|
||||
|
||||
from rare.models.options import options
|
||||
from rare.components.dialogs.launch_dialog import LaunchDialog
|
||||
from rare.components.main_window import MainWindow
|
||||
from rare.shared import RareCore
|
||||
|
@ -45,8 +46,8 @@ class Rare(RareApp):
|
|||
self.signals = RareCore.instance().signals()
|
||||
self.core = RareCore.instance().core()
|
||||
|
||||
lang = self.settings.value("language", self.core.language_code, type=str)
|
||||
self.load_translator(lang)
|
||||
language = self.settings.value(*options.language)
|
||||
self.load_translator(language)
|
||||
|
||||
# set Application name for settings
|
||||
self.main_window: Optional[MainWindow] = None
|
||||
|
|
|
@ -15,7 +15,7 @@ from rare.ui.components.dialogs.install_dialog import Ui_InstallDialog
|
|||
from rare.ui.components.dialogs.install_dialog_advanced import Ui_InstallDialogAdvanced
|
||||
from rare.utils.misc import format_size, icon
|
||||
from rare.widgets.collapsible_widget import CollapsibleFrame
|
||||
from rare.widgets.dialogs import ActionDialog, dialog_title_game
|
||||
from rare.widgets.dialogs import ActionDialog, game_title
|
||||
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
|
||||
from rare.widgets.selective_widget import SelectiveWidget
|
||||
|
||||
|
@ -74,8 +74,8 @@ class InstallDialog(ActionDialog):
|
|||
elif options.reset_sdl:
|
||||
header = self.tr("Modify")
|
||||
bicon = icon("fa.gear")
|
||||
self.setWindowTitle(dialog_title_game(header, rgame.app_title))
|
||||
self.setSubtitle(dialog_title_game(header, rgame.app_title))
|
||||
self.setWindowTitle(game_title(header, rgame.app_title))
|
||||
self.setSubtitle(game_title(header, rgame.app_title))
|
||||
|
||||
install_widget = QWidget(self)
|
||||
self.ui = Ui_InstallDialog()
|
||||
|
|
|
@ -2,14 +2,15 @@ import json
|
|||
from logging import getLogger
|
||||
from typing import Tuple
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl
|
||||
from PyQt5.QtCore import pyqtSignal, QUrl, QProcess, pyqtSlot
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtWidgets import QFrame, qApp, QFormLayout, QLineEdit
|
||||
from legendary.core import LegendaryCore
|
||||
from legendary.utils import webview_login
|
||||
|
||||
from rare.lgndr.core import LegendaryCore
|
||||
from rare.ui.components.dialogs.login.browser_login import Ui_BrowserLogin
|
||||
from rare.utils.misc import icon
|
||||
from rare.utils.paths import get_rare_executable
|
||||
from rare.widgets.indicator_edit import IndicatorLineEdit, IndicatorReasonsCommon
|
||||
|
||||
logger = getLogger("BrowserLogin")
|
||||
|
@ -43,6 +44,7 @@ class BrowserLogin(QFrame):
|
|||
self.ui.open_button.clicked.connect(self.open_browser)
|
||||
self.sid_edit.textChanged.connect(self.changed.emit)
|
||||
|
||||
@pyqtSlot()
|
||||
def copy_link(self):
|
||||
clipboard = qApp.clipboard()
|
||||
clipboard.setText(self.login_url)
|
||||
|
@ -79,12 +81,24 @@ class BrowserLogin(QFrame):
|
|||
except Exception as e:
|
||||
logger.warning(e)
|
||||
|
||||
@pyqtSlot()
|
||||
def open_browser(self):
|
||||
if not webview_login.webview_available:
|
||||
logger.warning("You don't have webengine installed, you will need to manually copy the authorizationCode.")
|
||||
QDesktopServices.openUrl(QUrl(self.login_url))
|
||||
else:
|
||||
if webview_login.do_webview_login(callback_code=self.core.auth_ex_token):
|
||||
cmd = get_rare_executable() + ["login", self.core.get_egl_version()]
|
||||
proc = QProcess(self)
|
||||
proc.start(cmd[0], cmd[1:])
|
||||
proc.waitForFinished(-1)
|
||||
out, err = (
|
||||
proc.readAllStandardOutput().data().decode("utf-8", "ignore"),
|
||||
proc.readAllStandardError().data().decode("utf-8", "ignore")
|
||||
)
|
||||
proc.deleteLater()
|
||||
|
||||
if out:
|
||||
self.core.auth_ex_token(out)
|
||||
logger.info("Successfully logged in as %s", {self.core.lgd.userdata['displayName']})
|
||||
self.success.emit()
|
||||
else:
|
||||
|
|
|
@ -11,7 +11,7 @@ from rare.models.install import MoveGameModel
|
|||
from rare.models.game import RareGame
|
||||
from rare.shared import RareCore
|
||||
from rare.utils.misc import path_size, format_size, icon
|
||||
from rare.widgets.dialogs import ActionDialog, dialog_title_game
|
||||
from rare.widgets.dialogs import ActionDialog, game_title
|
||||
from rare.widgets.elide_label import ElideLabel
|
||||
from rare.widgets.indicator_edit import PathEdit, IndicatorReasons, IndicatorReasonsCommon
|
||||
|
||||
|
@ -33,8 +33,8 @@ class MoveDialog(ActionDialog):
|
|||
def __init__(self, rgame: RareGame, parent=None):
|
||||
super(MoveDialog, self).__init__(parent=parent)
|
||||
header = self.tr("Move")
|
||||
self.setWindowTitle(dialog_title_game(header, rgame.app_title))
|
||||
self.setSubtitle(dialog_title_game(header, rgame.app_title))
|
||||
self.setWindowTitle(game_title(header, rgame.app_title))
|
||||
self.setSubtitle(game_title(header, rgame.app_title))
|
||||
|
||||
self.rcore = RareCore.instance()
|
||||
self.core = RareCore.instance().core()
|
||||
|
@ -135,7 +135,7 @@ class MoveDialog(ActionDialog):
|
|||
if not os.access(path, os.W_OK) or not os.access(self.rgame.install_path, os.W_OK):
|
||||
return helper_func(MovePathEditReasons.NO_WRITE_PERM)
|
||||
|
||||
if src_path == dst_path or src_path == dst_install_path:
|
||||
if src_path in {dst_path, dst_install_path}:
|
||||
return helper_func(MovePathEditReasons.SAME_DIR)
|
||||
|
||||
if str(src_path) in str(dst_path):
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5.QtWidgets import QLabel, QVBoxLayout, QLayout, QGroupBox
|
||||
from PyQt5.QtWidgets import QVBoxLayout, QGroupBox
|
||||
|
||||
from rare.models.game import RareGame
|
||||
from rare.models.install import SelectiveDownloadsModel
|
||||
from rare.utils.misc import icon
|
||||
from rare.widgets.dialogs import ButtonDialog, dialog_title_game
|
||||
from rare.widgets.dialogs import ButtonDialog, game_title
|
||||
from rare.widgets.selective_widget import SelectiveWidget
|
||||
|
||||
|
||||
|
@ -14,8 +14,8 @@ class SelectiveDialog(ButtonDialog):
|
|||
def __init__(self, rgame: RareGame, parent=None):
|
||||
super(SelectiveDialog, self).__init__(parent=parent)
|
||||
header = self.tr("Optional downloads for")
|
||||
self.setWindowTitle(dialog_title_game(header, rgame.app_title))
|
||||
self.setSubtitle(dialog_title_game(header, rgame.app_title))
|
||||
self.setWindowTitle(game_title(header, rgame.app_title))
|
||||
self.setSubtitle(game_title(header, rgame.app_title))
|
||||
|
||||
self.rgame = rgame
|
||||
self.selective_widget = SelectiveWidget(rgame, rgame.igame.platform, self)
|
||||
|
|
|
@ -7,7 +7,7 @@ from PyQt5.QtWidgets import (
|
|||
from rare.models.game import RareGame
|
||||
from rare.models.install import UninstallOptionsModel
|
||||
from rare.utils.misc import icon
|
||||
from rare.widgets.dialogs import ButtonDialog, dialog_title_game
|
||||
from rare.widgets.dialogs import ButtonDialog, game_title
|
||||
|
||||
|
||||
class UninstallDialog(ButtonDialog):
|
||||
|
@ -16,8 +16,8 @@ class UninstallDialog(ButtonDialog):
|
|||
def __init__(self, rgame: RareGame, options: UninstallOptionsModel, parent=None):
|
||||
super(UninstallDialog, self).__init__(parent=parent)
|
||||
header = self.tr("Uninstall")
|
||||
self.setWindowTitle(dialog_title_game(header, rgame.app_title))
|
||||
self.setSubtitle(dialog_title_game(header, rgame.app_title))
|
||||
self.setWindowTitle(game_title(header, rgame.app_title))
|
||||
self.setSubtitle(game_title(header, rgame.app_title))
|
||||
|
||||
self.keep_files = QCheckBox(self.tr("Keep files"))
|
||||
self.keep_files.setChecked(bool(options.keep_files))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -31,13 +31,13 @@ class MainTabWidget(QTabWidget):
|
|||
self.games_index = self.addTab(self.games_tab, self.tr("Games"))
|
||||
|
||||
# Downloads Tab after Games Tab to use populated RareCore games list
|
||||
if not self.args.offline:
|
||||
self.downloads_tab = DownloadsTab(self)
|
||||
self.downloads_index = self.addTab(self.downloads_tab, "")
|
||||
self.downloads_tab.update_title.connect(self.__on_downloads_update_title)
|
||||
self.downloads_tab.update_queues_count()
|
||||
self.setTabEnabled(self.downloads_index, not self.args.offline)
|
||||
self.downloads_tab = DownloadsTab(self)
|
||||
self.downloads_index = self.addTab(self.downloads_tab, "")
|
||||
self.downloads_tab.update_title.connect(self.__on_downloads_update_title)
|
||||
self.downloads_tab.update_queues_count()
|
||||
self.setTabEnabled(self.downloads_index, not self.args.offline)
|
||||
|
||||
if not self.args.offline:
|
||||
self.store_tab = Shop(self.core)
|
||||
self.store_index = self.addTab(self.store_tab, self.tr("Store (Beta)"))
|
||||
self.setTabEnabled(self.store_index, not self.args.offline)
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -55,7 +55,7 @@ class DlThread(QThread):
|
|||
if result.code == DlResultCode.FINISHED:
|
||||
self.rgame.set_installed(True)
|
||||
self.rgame.state = RareGame.State.IDLE
|
||||
self.rgame.signals.progress.finish.emit(not result.code == DlResultCode.FINISHED)
|
||||
self.rgame.signals.progress.finish.emit(result.code != DlResultCode.FINISHED)
|
||||
self.result.emit(result)
|
||||
|
||||
def __status_callback(self, status: UIUpdate):
|
||||
|
@ -125,15 +125,14 @@ class DlThread(QThread):
|
|||
dlcs = self.core.get_dlc_for_game(self.item.download.igame.app_name)
|
||||
if dlcs and not self.item.options.skip_dlcs:
|
||||
result.dlcs = []
|
||||
for dlc in dlcs:
|
||||
result.dlcs.append(
|
||||
{
|
||||
"app_name": dlc.app_name,
|
||||
"app_title": dlc.app_title,
|
||||
"app_version": dlc.app_version(self.item.options.platform),
|
||||
}
|
||||
)
|
||||
|
||||
result.dlcs.extend(
|
||||
{
|
||||
"app_name": dlc.app_name,
|
||||
"app_title": dlc.app_title,
|
||||
"app_version": dlc.app_version(self.item.options.platform),
|
||||
}
|
||||
for dlc in dlcs
|
||||
)
|
||||
if (
|
||||
self.item.download.game.supports_cloud_saves
|
||||
or self.item.download.game.supports_mac_cloud_saves
|
||||
|
|
|
@ -12,10 +12,9 @@ from rare.shared import (
|
|||
ImageManagerSingleton,
|
||||
)
|
||||
from rare.shared import RareCore
|
||||
from rare.widgets.library_layout import LibraryLayout
|
||||
from rare.widgets.sliding_stack import SlidingStackedWidget
|
||||
from rare.models.options import options
|
||||
from .game_info import GameInfoTabs
|
||||
from .game_widgets import LibraryWidgetController
|
||||
from .game_widgets import LibraryWidgetController, LibraryFilter, LibraryOrder, LibraryView
|
||||
from .game_widgets.icon_game_widget import IconGameWidget
|
||||
from .game_widgets.list_game_widget import ListGameWidget
|
||||
from .head_bar import GameListHeadBar
|
||||
|
@ -53,51 +52,22 @@ class GamesTab(QStackedWidget):
|
|||
self.integrations_page.back_clicked.connect(lambda: self.setCurrentWidget(self.games_page))
|
||||
self.addWidget(self.integrations_page)
|
||||
|
||||
self.view_stack = SlidingStackedWidget(self.games_page)
|
||||
self.view_stack.setFrameStyle(QFrame.NoFrame)
|
||||
self.view_scroll = QScrollArea(self.games_page)
|
||||
self.view_scroll.setWidgetResizable(True)
|
||||
self.view_scroll.setFrameShape(QFrame.StyledPanel)
|
||||
self.view_scroll.horizontalScrollBar().setDisabled(True)
|
||||
|
||||
self.icon_view_scroll = QScrollArea(self.view_stack)
|
||||
self.icon_view_scroll.setWidgetResizable(True)
|
||||
self.icon_view_scroll.setFrameShape(QFrame.StyledPanel)
|
||||
self.icon_view_scroll.horizontalScrollBar().setDisabled(True)
|
||||
library_view = LibraryView(self.settings.value(*options.library_view))
|
||||
self.library_controller = LibraryWidgetController(library_view, self.view_scroll)
|
||||
games_page_layout.addWidget(self.view_scroll)
|
||||
|
||||
self.list_view_scroll = QScrollArea(self.view_stack)
|
||||
self.list_view_scroll.setWidgetResizable(True)
|
||||
self.list_view_scroll.setFrameShape(QFrame.StyledPanel)
|
||||
self.list_view_scroll.horizontalScrollBar().setDisabled(True)
|
||||
|
||||
self.icon_view = QWidget(self.icon_view_scroll)
|
||||
icon_view_layout = LibraryLayout(self.icon_view)
|
||||
icon_view_layout.setSpacing(9)
|
||||
icon_view_layout.setContentsMargins(0, 13, 0, 13)
|
||||
icon_view_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
self.list_view = QWidget(self.list_view_scroll)
|
||||
list_view_layout = QVBoxLayout(self.list_view)
|
||||
list_view_layout.setContentsMargins(3, 3, 9, 3)
|
||||
list_view_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
self.library_controller = LibraryWidgetController(self.icon_view, self.list_view, self)
|
||||
self.icon_view_scroll.setWidget(self.icon_view)
|
||||
self.list_view_scroll.setWidget(self.list_view)
|
||||
self.view_stack.addWidget(self.icon_view_scroll)
|
||||
self.view_stack.addWidget(self.list_view_scroll)
|
||||
games_page_layout.addWidget(self.view_stack)
|
||||
|
||||
if not self.settings.value("icon_view", True, bool):
|
||||
self.view_stack.setCurrentWidget(self.list_view_scroll)
|
||||
self.head_bar.view.list()
|
||||
else:
|
||||
self.view_stack.setCurrentWidget(self.icon_view_scroll)
|
||||
|
||||
self.head_bar.search_bar.textChanged.connect(lambda x: self.filter_games("", x))
|
||||
self.head_bar.search_bar.textChanged.connect(self.search_games)
|
||||
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.view.toggled.connect(self.toggle_view)
|
||||
|
||||
self.active_filter: str = self.head_bar.filter.currentData(Qt.UserRole)
|
||||
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)
|
||||
|
||||
# signals
|
||||
self.signals.game.installed.connect(self.update_count_games_label)
|
||||
|
@ -114,11 +84,8 @@ class GamesTab(QStackedWidget):
|
|||
|
||||
@pyqtSlot()
|
||||
def scroll_to_top(self):
|
||||
self.icon_view_scroll.verticalScrollBar().setSliderPosition(
|
||||
self.icon_view_scroll.verticalScrollBar().minimum()
|
||||
)
|
||||
self.list_view_scroll.verticalScrollBar().setSliderPosition(
|
||||
self.list_view_scroll.verticalScrollBar().minimum()
|
||||
self.view_scroll.verticalScrollBar().setSliderPosition(
|
||||
self.view_scroll.verticalScrollBar().minimum()
|
||||
)
|
||||
|
||||
@pyqtSlot()
|
||||
|
@ -139,8 +106,8 @@ class GamesTab(QStackedWidget):
|
|||
|
||||
@pyqtSlot(RareGame)
|
||||
def show_game_info(self, rgame):
|
||||
self.setCurrentWidget(self.game_info_page)
|
||||
self.game_info_page.update_game(rgame)
|
||||
self.setCurrentWidget(self.game_info_page)
|
||||
|
||||
@pyqtSlot()
|
||||
def update_count_games_label(self):
|
||||
|
@ -151,42 +118,38 @@ class GamesTab(QStackedWidget):
|
|||
|
||||
def setup_game_list(self):
|
||||
for rgame in self.rcore.games:
|
||||
icon_widget, list_widget = self.add_library_widget(rgame)
|
||||
if not icon_widget or not list_widget:
|
||||
widget = self.add_library_widget(rgame)
|
||||
if not widget:
|
||||
logger.warning("Excluding %s from the game list", rgame.app_title)
|
||||
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):
|
||||
try:
|
||||
icon_widget, list_widget = self.library_controller.add_game(rgame)
|
||||
widget = self.library_controller.add_game(rgame)
|
||||
except Exception as e:
|
||||
logger.error("Could not add widget for %s to library: %s", rgame.app_name, e)
|
||||
return None, None
|
||||
icon_widget.show_info.connect(self.show_game_info)
|
||||
list_widget.show_info.connect(self.show_game_info)
|
||||
return icon_widget, list_widget
|
||||
return None
|
||||
widget.show_info.connect(self.show_game_info)
|
||||
return widget
|
||||
|
||||
@pyqtSlot(str)
|
||||
@pyqtSlot(str, str)
|
||||
def filter_games(self, filter_name="all", search_text: str = ""):
|
||||
def search_games(self, search_text: str = ""):
|
||||
self.filter_games(self.head_bar.current_filter(), search_text)
|
||||
|
||||
@pyqtSlot(object)
|
||||
@pyqtSlot(object, 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
|
||||
self.library_controller.filter_game_views(library_filter, search_text.lower())
|
||||
|
||||
self.library_controller.filter_list(filter_name, search_text.lower())
|
||||
@pyqtSlot(object)
|
||||
@pyqtSlot(object, 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
|
||||
|
||||
def toggle_view(self):
|
||||
self.settings.setValue("icon_view", not self.head_bar.view.isChecked())
|
||||
|
||||
if not self.head_bar.view.isChecked():
|
||||
self.view_stack.slideInWidget(self.icon_view_scroll)
|
||||
else:
|
||||
self.view_stack.slideInWidget(self.list_view_scroll)
|
||||
self.library_controller.order_game_views(library_order, search_text.lower())
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
from PyQt5.QtGui import QKeyEvent
|
||||
from PyQt5.QtWidgets import QTreeView
|
||||
|
||||
|
@ -64,8 +64,8 @@ class GameInfoTabs(SideTabWidget):
|
|||
|
||||
self.setCurrentIndex(self.info_index)
|
||||
|
||||
def keyPressEvent(self, e: QKeyEvent):
|
||||
if e.key() == Qt.Key_Escape:
|
||||
def keyPressEvent(self, a0: QKeyEvent):
|
||||
if a0.key() == Qt.Key_Escape:
|
||||
self.back_clicked.emit()
|
||||
|
||||
|
||||
|
|
|
@ -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,15 +19,16 @@ 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
|
||||
|
||||
logger = getLogger("CloudWidget")
|
||||
logger = getLogger("CloudSaves")
|
||||
|
||||
|
||||
class CloudSaves(QWidget, SideTabContents):
|
||||
|
@ -114,30 +115,32 @@ class CloudSaves(QWidget, SideTabContents):
|
|||
def compute_save_path(self):
|
||||
if self.rgame.is_installed and self.rgame.game.supports_cloud_saves:
|
||||
try:
|
||||
new_path = self.core.get_save_path(self.rgame.app_name)
|
||||
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"):
|
||||
del resolver
|
||||
self.cloud_save_path_edit.setText("")
|
||||
QMessageBox.warning(self, "Warning", "No wine prefix selected. Please set it in settings")
|
||||
return
|
||||
resolver = WineSavePathResolver(self.core, self.rgame)
|
||||
# if not resolver.environ.get("WINEPREFIX"):
|
||||
# del resolver
|
||||
# self.cloud_save_path_edit.setText("")
|
||||
# QMessageBox.warning(self, "Warning", "No wine prefix selected. Please set it in settings")
|
||||
# return
|
||||
self.cloud_save_path_edit.setText(self.tr("Loading..."))
|
||||
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):
|
||||
logger.info(f"Wine resolver finished for {app_name}. Computed save path: {path}")
|
||||
@pyqtSlot(str, str)
|
||||
def __on_wine_resolver_result(self, path, app_name):
|
||||
logger.info("Wine resolver finished for %s", app_name)
|
||||
logger.info("Computed save path: %s", path)
|
||||
if app_name == self.rgame.app_name:
|
||||
self.cloud_save_path_edit.setDisabled(False)
|
||||
self.compute_save_path_button.setDisabled(False)
|
||||
|
@ -158,8 +161,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 (
|
||||
|
|
|
@ -20,7 +20,7 @@ from rare.models.game import RareGame
|
|||
from rare.shared import RareCore
|
||||
from rare.shared.workers import VerifyWorker, MoveWorker
|
||||
from rare.ui.components.tabs.games.game_info.game_info import Ui_GameInfo
|
||||
from rare.utils.misc import format_size, icon
|
||||
from rare.utils.misc import format_size, icon, style_hyperlink
|
||||
from rare.widgets.image_widget import ImageWidget, ImageSize
|
||||
from rare.widgets.side_tab import SideTabContents
|
||||
from rare.components.dialogs.move_dialog import MoveDialog, is_game_dir
|
||||
|
@ -302,7 +302,12 @@ class GameInfo(QWidget, SideTabContents):
|
|||
self.ui.grade.setDisabled(
|
||||
self.rgame.is_unreal or platform.system() == "Windows"
|
||||
)
|
||||
self.ui.grade.setText(self.steam_grade_ratings[self.rgame.steam_grade()])
|
||||
self.ui.grade.setText(
|
||||
style_hyperlink(
|
||||
f"https://www.protondb.com/app/{self.rgame.steam_appid}",
|
||||
self.steam_grade_ratings[self.rgame.steam_grade()]
|
||||
)
|
||||
)
|
||||
|
||||
self.ui.install_button.setEnabled(
|
||||
(not self.rgame.is_installed or self.rgame.is_non_asset) and self.rgame.is_idle
|
||||
|
|
|
@ -1,64 +1,117 @@
|
|||
import os.path
|
||||
import platform
|
||||
import platform as pf
|
||||
from logging import getLogger
|
||||
from typing import Tuple
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QLabel, QFileDialog
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QShowEvent
|
||||
from PyQt5.QtWidgets import QFileDialog, QComboBox, QLineEdit
|
||||
from legendary.models.game import Game, InstalledGame
|
||||
|
||||
from rare.components.tabs.settings import DefaultGameSettings
|
||||
from rare.components.tabs.settings.widgets.pre_launch import PreLaunchSettings
|
||||
from rare.components.tabs.settings.widgets.env_vars import EnvVars
|
||||
from rare.components.tabs.settings.widgets.game import GameSettingsBase
|
||||
from rare.components.tabs.settings.widgets.launch import LaunchSettingsBase
|
||||
from rare.components.tabs.settings.widgets.overlay import DxvkSettings
|
||||
from rare.components.tabs.settings.widgets.wrappers import WrapperSettings
|
||||
from rare.models.game import RareGame
|
||||
from rare.utils import config_helper
|
||||
from rare.widgets.side_tab import SideTabContents
|
||||
from rare.utils import config_helper as config
|
||||
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
|
||||
|
||||
if pf.system() != "Windows":
|
||||
from rare.components.tabs.settings.widgets.wine import WineSettings
|
||||
|
||||
if pf.system() in {"Linux", "FreeBSD"}:
|
||||
from rare.components.tabs.settings.widgets.proton import ProtonSettings
|
||||
from rare.components.tabs.settings.widgets.overlay import MangoHudSettings
|
||||
|
||||
logger = getLogger("GameSettings")
|
||||
|
||||
|
||||
class GameSettings(DefaultGameSettings, SideTabContents):
|
||||
class GameWrapperSettings(WrapperSettings):
|
||||
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
|
||||
)
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.ui.skip_update.currentIndexChanged.connect(
|
||||
lambda x: self.update_combobox("skip_update_check", x)
|
||||
)
|
||||
self.ui.offline.currentIndexChanged.connect(
|
||||
lambda x: self.update_combobox("offline", x)
|
||||
)
|
||||
self.ui.launch_params.textChanged.connect(
|
||||
lambda x: self.line_edit_save_callback("start_params", x)
|
||||
)
|
||||
def load_settings(self, app_name: str):
|
||||
self.app_name = app_name
|
||||
|
||||
self.override_exe_edit = PathEdit(
|
||||
file_mode=QFileDialog.ExistingFile,
|
||||
name_filters=["*.exe", "*.app"],
|
||||
placeholder=self.tr("Relative path to launch executable"),
|
||||
edit_func=self.override_exe_edit_callback,
|
||||
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.game_settings_layout.setAlignment(Qt.AlignTop)
|
||||
class GameLaunchSettings(LaunchSettingsBase):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(GameLaunchSettings, self).__init__(GameWrapperSettings, parent=parent)
|
||||
|
||||
self.game: Game = None
|
||||
self.igame: InstalledGame = None
|
||||
|
||||
def override_exe_edit_callback(self, path: str) -> Tuple[bool, str, int]:
|
||||
self.skip_update_combo = QComboBox(self)
|
||||
self.skip_update_combo.addItem(self.tr("Default"), None)
|
||||
self.skip_update_combo.addItem(self.tr("No"), "false")
|
||||
self.skip_update_combo.addItem(self.tr("Yes"), "true")
|
||||
self.skip_update_combo.currentIndexChanged.connect(self.__skip_update_changed)
|
||||
|
||||
self.offline_combo = QComboBox(self)
|
||||
self.offline_combo.addItem(self.tr("Default"), None)
|
||||
self.offline_combo.addItem(self.tr("No"), "false")
|
||||
self.offline_combo.addItem(self.tr("Yes"), "true")
|
||||
self.offline_combo.currentIndexChanged.connect(self.__offline_changed)
|
||||
|
||||
self.override_exe_edit = PathEdit(
|
||||
file_mode=QFileDialog.ExistingFile,
|
||||
name_filters=["*.exe", "*.app"],
|
||||
placeholder=self.tr("Relative path to the replacement executable"),
|
||||
edit_func=self.__override_exe_edit_callback,
|
||||
save_func=self.__override_exe_save_callback,
|
||||
parent=self
|
||||
)
|
||||
|
||||
self.launch_params_edit = QLineEdit(self)
|
||||
self.launch_params_edit.setPlaceholderText(self.tr("Game specific command line arguments"))
|
||||
self.launch_params_edit.textChanged.connect(self.__launch_params_changed)
|
||||
|
||||
self.main_layout.insertRow(0, self.tr("Skip update check"), self.skip_update_combo)
|
||||
self.main_layout.insertRow(1, self.tr("Offline mode"), self.offline_combo)
|
||||
self.main_layout.insertRow(2, self.tr("Launch parameters"), self.launch_params_edit)
|
||||
self.main_layout.insertRow(3, self.tr("Override executable"), self.override_exe_edit)
|
||||
|
||||
def showEvent(self, a0: QShowEvent):
|
||||
if a0.spontaneous():
|
||||
return super().showEvent(a0)
|
||||
|
||||
skip_update = config.get_option(self.app_name, "skip_update_check", fallback=None)
|
||||
self.skip_update_combo.setCurrentIndex(self.offline_combo.findData(skip_update, Qt.UserRole))
|
||||
|
||||
offline = config.get_option(self.app_name, "offline", fallback=None)
|
||||
self.offline_combo.setCurrentIndex(self.offline_combo.findData(offline, Qt.UserRole))
|
||||
|
||||
if self.igame:
|
||||
self.offline_combo.setEnabled(self.igame.can_run_offline)
|
||||
self.override_exe_edit.set_root(self.igame.install_path)
|
||||
else:
|
||||
self.offline_combo.setEnabled(False)
|
||||
self.override_exe_edit.set_root("")
|
||||
|
||||
launch_params = config.get_option(self.app_name, "start_params", "")
|
||||
self.launch_params_edit.setText(launch_params)
|
||||
|
||||
override_exe = config.get_option(self.app_name, "override_exe", fallback="")
|
||||
self.override_exe_edit.setText(override_exe)
|
||||
|
||||
return super().showEvent(a0)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def __skip_update_changed(self, index):
|
||||
data = self.skip_update_combo.itemData(index, Qt.UserRole)
|
||||
config.save_option(self.app_name, "skip_update_check", data)
|
||||
|
||||
def __override_exe_edit_callback(self, path: str) -> Tuple[bool, str, int]:
|
||||
if not path or self.igame is None:
|
||||
return True, path, IndicatorReasonsCommon.VALID
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(self.igame.install_path, path)
|
||||
if self.igame.install_path not in path:
|
||||
# lk: Compare paths through python's commonpath because in windows we
|
||||
# cannot compare as strings
|
||||
# antonia disapproves of this
|
||||
if os.path.commonpath([self.igame.install_path, path]) != self.igame.install_path:
|
||||
return False, self.igame.install_path, IndicatorReasonsCommon.WRONG_PATH
|
||||
if not os.path.exists(path):
|
||||
return False, path, IndicatorReasonsCommon.WRONG_PATH
|
||||
|
@ -68,72 +121,80 @@ class GameSettings(DefaultGameSettings, SideTabContents):
|
|||
path = os.path.relpath(path, self.igame.install_path)
|
||||
return True, path, IndicatorReasonsCommon.VALID
|
||||
|
||||
def override_exe_save_callback(self, path: str):
|
||||
self.line_edit_save_callback("override_exe", path)
|
||||
def __override_exe_save_callback(self, path: str):
|
||||
config.save_option(self.app_name, "override_exe", path)
|
||||
|
||||
def line_edit_save_callback(self, option, value) -> None:
|
||||
if value:
|
||||
config_helper.add_option(self.game.app_name, option, value)
|
||||
else:
|
||||
config_helper.remove_option(self.game.app_name, option)
|
||||
config_helper.save_config()
|
||||
@pyqtSlot(int)
|
||||
def __offline_changed(self, index):
|
||||
data = self.skip_update_combo.itemData(index, Qt.UserRole)
|
||||
config.save_option(self.app_name, "offline", data)
|
||||
|
||||
def update_combobox(self, option, index):
|
||||
if self.change:
|
||||
# remove section
|
||||
if index:
|
||||
if index == 1:
|
||||
config_helper.add_option(self.game.app_name, option, "true")
|
||||
if index == 2:
|
||||
config_helper.add_option(self.game.app_name, option, "false")
|
||||
else:
|
||||
config_helper.remove_option(self.game.app_name, option)
|
||||
config_helper.save_config()
|
||||
def __launch_params_changed(self, value) -> None:
|
||||
config.save_option(self.app_name, "start_params", value)
|
||||
|
||||
def load_settings(self, rgame: RareGame):
|
||||
self.change = False
|
||||
# FIXME: Use RareGame for the rest of the code
|
||||
app_name = rgame.app_name
|
||||
super(GameSettings, self).load_settings(app_name)
|
||||
self.game = rgame.game
|
||||
self.igame = rgame.igame
|
||||
if self.igame:
|
||||
if self.igame.can_run_offline:
|
||||
offline = self.core.lgd.config.get(self.game.app_name, "offline", fallback="unset")
|
||||
if offline == "true":
|
||||
self.ui.offline.setCurrentIndex(1)
|
||||
elif offline == "false":
|
||||
self.ui.offline.setCurrentIndex(2)
|
||||
else:
|
||||
self.ui.offline.setCurrentIndex(0)
|
||||
self.app_name = rgame.app_name
|
||||
self.wrappers_widget.load_settings(rgame.app_name)
|
||||
|
||||
self.ui.offline.setEnabled(True)
|
||||
else:
|
||||
self.ui.offline.setEnabled(False)
|
||||
self.override_exe_edit.set_root(self.igame.install_path)
|
||||
|
||||
if pf.system() != "Windows":
|
||||
|
||||
class GameWineSettings(WineSettings):
|
||||
def load_settings(self, app_name):
|
||||
self.app_name = app_name
|
||||
|
||||
|
||||
if pf.system() in {"Linux", "FreeBSD"}:
|
||||
|
||||
class GameProtonSettings(ProtonSettings):
|
||||
def load_settings(self, app_name: str):
|
||||
self.app_name = app_name
|
||||
|
||||
class GameMangoHudSettings(MangoHudSettings):
|
||||
def load_settings(self, app_name: str):
|
||||
self.app_name = app_name
|
||||
|
||||
|
||||
class GameDxvkSettings(DxvkSettings):
|
||||
def load_settings(self, app_name: str):
|
||||
self.app_name = app_name
|
||||
|
||||
|
||||
class GameEnvVars(EnvVars):
|
||||
def load_settings(self, app_name):
|
||||
self.app_name = app_name
|
||||
|
||||
|
||||
class GameSettings(GameSettingsBase):
|
||||
def __init__(self, parent=None):
|
||||
if pf.system() in {"Linux", "FreeBSD"}:
|
||||
super(GameSettings, self).__init__(
|
||||
GameLaunchSettings, GameDxvkSettings, GameEnvVars,
|
||||
GameWineSettings, GameProtonSettings, GameMangoHudSettings,
|
||||
parent=parent
|
||||
)
|
||||
elif pf.system() != "Windows":
|
||||
super(GameSettings, self).__init__(
|
||||
GameLaunchSettings, GameDxvkSettings, GameEnvVars,
|
||||
GameWineSettings,
|
||||
parent=parent
|
||||
)
|
||||
else:
|
||||
self.ui.offline.setEnabled(False)
|
||||
self.override_exe_edit.set_root("")
|
||||
super(GameSettings, self).__init__(
|
||||
GameLaunchSettings, GameDxvkSettings, GameEnvVars,
|
||||
parent=parent
|
||||
)
|
||||
|
||||
skip_update = self.core.lgd.config.get(self.game.app_name, "skip_update_check", fallback="unset")
|
||||
if skip_update == "true":
|
||||
self.ui.skip_update.setCurrentIndex(1)
|
||||
elif skip_update == "false":
|
||||
self.ui.skip_update.setCurrentIndex(2)
|
||||
else:
|
||||
self.ui.skip_update.setCurrentIndex(0)
|
||||
|
||||
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)
|
||||
else:
|
||||
self.ui.linux_settings_widget.setVisible(True)
|
||||
|
||||
self.ui.launch_params.setText(self.core.lgd.config.get(self.game.app_name, "start_params", fallback=""))
|
||||
self.override_exe_edit.setText(
|
||||
self.core.lgd.config.get(self.game.app_name, "override_exe", fallback="")
|
||||
)
|
||||
self.pre_launch_settings.load_settings(app_name)
|
||||
|
||||
self.change = True
|
||||
def load_settings(self, rgame: RareGame):
|
||||
self.set_title.emit(rgame.app_title)
|
||||
self.app_name = rgame.app_name
|
||||
self.launch.load_settings(rgame)
|
||||
if pf.system() != "Windows":
|
||||
self.wine.load_settings(rgame.app_name)
|
||||
if pf.system() in {"Linux", "FreeBSD"}:
|
||||
self.proton_tool.load_settings(rgame.app_name)
|
||||
self.mangohud.load_settings(rgame.app_name)
|
||||
self.dxvk.load_settings(rgame.app_name)
|
||||
self.env_vars.load_settings(rgame.app_name)
|
||||
|
|
|
@ -1,55 +1,50 @@
|
|||
from typing import Tuple, List, Union, Optional
|
||||
from abc import abstractmethod
|
||||
from typing import Tuple, List, Union, Type, TypeVar
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
from PyQt5.QtWidgets import QWidget
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, Qt
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QScrollArea
|
||||
|
||||
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, LibraryView
|
||||
from rare.shared import RareCore
|
||||
from rare.widgets.library_layout import LibraryLayout
|
||||
from .icon_game_widget import IconGameWidget
|
||||
from .list_game_widget import ListGameWidget
|
||||
|
||||
ViewWidget = TypeVar("ViewWidget", IconGameWidget, ListGameWidget)
|
||||
|
||||
class LibraryWidgetController(QObject):
|
||||
def __init__(self, icon_container: QWidget, list_container: QWidget, parent: QWidget = None):
|
||||
super(LibraryWidgetController, self).__init__(parent=parent)
|
||||
self._icon_container: QWidget = icon_container
|
||||
self._list_container: QWidget = list_container
|
||||
self.rcore = RareCore.instance()
|
||||
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)
|
||||
class ViewContainer(QWidget):
|
||||
def __init__(self, rcore: RareCore, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self.rcore: RareCore = rcore
|
||||
|
||||
def add_game(self, rgame: RareGame):
|
||||
return self.add_widgets(rgame)
|
||||
|
||||
def add_widgets(self, rgame: RareGame) -> Tuple[IconGameWidget, ListGameWidget]:
|
||||
icon_widget = IconGameWidget(rgame, self._icon_container)
|
||||
list_widget = ListGameWidget(rgame, self._list_container)
|
||||
return icon_widget, list_widget
|
||||
def _add_widget(self, widget_type: Type[ViewWidget], rgame: RareGame) -> ViewWidget:
|
||||
widget = widget_type(rgame, self)
|
||||
self.layout().addWidget(widget)
|
||||
return widget
|
||||
|
||||
@staticmethod
|
||||
def __visibility(widget: Union[IconGameWidget,ListGameWidget], filter_name, search_text) -> Tuple[bool, float]:
|
||||
if filter_name == "hidden":
|
||||
def __visibility(widget: ViewWidget, 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,74 +59,159 @@ class LibraryWidgetController(QObject):
|
|||
|
||||
return visible, opacity
|
||||
|
||||
def filter_list(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:
|
||||
visibility, opacity = self.__visibility(iw, filter_name, search_text)
|
||||
def _filter_view(self, widget_type: Type[ViewWidget], filter_by: LibraryFilter = LibraryFilter.ALL, search_text: str = ""):
|
||||
widgets = self.findChildren(widget_type)
|
||||
for iw in widgets:
|
||||
visibility, opacity = self.__visibility(iw, filter_by, search_text)
|
||||
iw.setOpacity(opacity)
|
||||
iw.setVisible(visibility)
|
||||
for lw in list_widgets:
|
||||
visibility, opacity = self.__visibility(lw, filter_name, search_text)
|
||||
lw.setOpacity(opacity)
|
||||
lw.setVisible(visibility)
|
||||
self.sort_list(search_text)
|
||||
|
||||
def _update_view(self, widget_type: Type[ViewWidget]):
|
||||
widgets = self.findChildren(widget_type)
|
||||
app_names = {iw.rgame.app_name for iw in widgets}
|
||||
games = list(self.rcore.games)
|
||||
game_app_names = {g.app_name for g in games}
|
||||
new_app_names = game_app_names.difference(app_names)
|
||||
for app_name in new_app_names:
|
||||
game = self.rcore.get_game(app_name)
|
||||
w = widget_type(game, self)
|
||||
self.layout().addWidget(w)
|
||||
|
||||
def _find_widget(self, widget_type: Type[ViewWidget], app_name: str) -> ViewWidget:
|
||||
w = self.findChild(widget_type, app_name)
|
||||
return w
|
||||
|
||||
@abstractmethod
|
||||
def order_view(self):
|
||||
pass
|
||||
|
||||
|
||||
class IconViewContainer(ViewContainer):
|
||||
def __init__(self, rcore: RareCore, parent=None):
|
||||
super().__init__(rcore, parent=parent)
|
||||
view_layout = LibraryLayout(self)
|
||||
view_layout.setSpacing(9)
|
||||
view_layout.setContentsMargins(0, 13, 0, 13)
|
||||
view_layout.setAlignment(Qt.AlignTop)
|
||||
self.setLayout(view_layout)
|
||||
|
||||
def add_widget(self, rgame: RareGame) -> IconGameWidget:
|
||||
return self._add_widget(IconGameWidget, rgame)
|
||||
|
||||
def filter_view(self, filter_by: LibraryFilter = LibraryFilter.ALL, search_text: str = ""):
|
||||
self._filter_view(IconGameWidget, filter_by, search_text)
|
||||
|
||||
def update_view(self):
|
||||
self._update_view(IconGameWidget)
|
||||
|
||||
def find_widget(self, app_name: str) -> ViewWidget:
|
||||
return self._find_widget(IconGameWidget, app_name)
|
||||
|
||||
def order_view(self, order_by: LibraryOrder = LibraryOrder.TITLE, search_text: str = ""):
|
||||
if search_text:
|
||||
self.layout().sort(
|
||||
lambda x: (search_text not in x.widget().rgame.app_title.lower(),)
|
||||
)
|
||||
else:
|
||||
if (newest := order_by == LibraryOrder.NEWEST) or order_by == LibraryOrder.OLDEST:
|
||||
# Sort by grant date
|
||||
self.layout().sort(
|
||||
key=lambda x: (x.widget().rgame.is_installed, not x.widget().rgame.is_non_asset, x.widget().rgame.grant_date()),
|
||||
reverse=newest,
|
||||
)
|
||||
elif order_by == LibraryOrder.RECENT:
|
||||
# Sort by recently played
|
||||
self.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,
|
||||
)
|
||||
else:
|
||||
# Sort by title
|
||||
self.layout().sort(
|
||||
key=lambda x: (not x.widget().rgame.is_installed, x.widget().rgame.is_non_asset, x.widget().rgame.app_title)
|
||||
)
|
||||
|
||||
|
||||
class ListViewContainer(ViewContainer):
|
||||
def __init__(self, rcore, parent=None):
|
||||
super().__init__(rcore, parent=parent)
|
||||
view_layout = QVBoxLayout(self)
|
||||
view_layout.setContentsMargins(3, 3, 9, 3)
|
||||
view_layout.setAlignment(Qt.AlignTop)
|
||||
self.setLayout(view_layout)
|
||||
|
||||
def add_widget(self, rgame: RareGame) -> ListGameWidget:
|
||||
return self._add_widget(ListGameWidget, rgame)
|
||||
|
||||
def filter_view(self, filter_by: LibraryFilter = LibraryFilter.ALL, search_text: str = ""):
|
||||
self._filter_view(ListGameWidget, filter_by, search_text)
|
||||
|
||||
def update_view(self):
|
||||
self._update_view(ListGameWidget)
|
||||
|
||||
def find_widget(self, app_name: str) -> ViewWidget:
|
||||
return self._find_widget(ListGameWidget, app_name)
|
||||
|
||||
def order_view(self, order_by: LibraryOrder = LibraryOrder.TITLE, search_text: str = ""):
|
||||
list_widgets = self.findChildren(ListGameWidget)
|
||||
if search_text:
|
||||
list_widgets.sort(key=lambda x: (search_text not in x.rgame.app_title.lower(),))
|
||||
else:
|
||||
if (newest := order_by == LibraryOrder.NEWEST) or order_by == LibraryOrder.OLDEST:
|
||||
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:
|
||||
list_widgets.sort(
|
||||
key=lambda x: (not x.rgame.is_installed, x.rgame.is_non_asset, x.rgame.metadata.last_played),
|
||||
reverse=True,
|
||||
)
|
||||
else:
|
||||
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.layout().insertWidget(idx, wl)
|
||||
|
||||
|
||||
class LibraryWidgetController(QObject):
|
||||
def __init__(self, view: LibraryView, parent: QScrollArea = None):
|
||||
super(LibraryWidgetController, self).__init__(parent=parent)
|
||||
self.rcore = RareCore.instance()
|
||||
self.core: LegendaryCore = self.rcore.core()
|
||||
self.signals: GlobalSignals = self.rcore.signals()
|
||||
|
||||
if view == LibraryView.COVER:
|
||||
self._container: IconViewContainer = IconViewContainer(self.rcore, parent)
|
||||
else:
|
||||
self._container: ListViewContainer = ListViewContainer(self.rcore, parent)
|
||||
parent.setWidget(self._container)
|
||||
|
||||
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)
|
||||
|
||||
def add_widgets(self, rgame: RareGame) -> ViewWidget:
|
||||
return self._container.add_widget(rgame)
|
||||
|
||||
def filter_game_views(self, filter_by: LibraryFilter = LibraryFilter.ALL, search_text: str = ""):
|
||||
self._container.filter_view(filter_by, 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,
|
||||
)
|
||||
)
|
||||
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(),))
|
||||
else:
|
||||
list_widgets.sort(
|
||||
# Sort by grant date
|
||||
# key=lambda x: (x.rgame.is_installed, not x.rgame.is_non_asset, x.rgame.grant_date()), reverse=True
|
||||
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)
|
||||
def order_game_views(self, order_by: LibraryOrder = LibraryOrder.TITLE, search_text: str = ""):
|
||||
self._container.order_view(order_by, search_text)
|
||||
|
||||
@pyqtSlot()
|
||||
@pyqtSlot(list)
|
||||
def update_list(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)
|
||||
list_widgets = self._list_container.findChildren(ListGameWidget)
|
||||
icon_app_names = set([iw.rgame.app_name for iw in icon_widgets])
|
||||
list_app_names = set([lw.rgame.app_name for lw in list_widgets])
|
||||
games = list(self.rcore.games)
|
||||
game_app_names = set([g.app_name for g in games])
|
||||
new_icon_app_names = game_app_names.difference(icon_app_names)
|
||||
new_list_app_names = game_app_names.difference(list_app_names)
|
||||
for app_name in new_icon_app_names:
|
||||
game = self.rcore.get_game(app_name)
|
||||
iw = IconGameWidget(game)
|
||||
self._icon_container.layout().addWidget(iw)
|
||||
for app_name in new_list_app_names:
|
||||
game = self.rcore.get_game(app_name)
|
||||
lw = ListGameWidget(game)
|
||||
self._list_container.layout().addWidget(lw)
|
||||
self.sort_list()
|
||||
def update_game_views(self, app_names: List[str] = None):
|
||||
if app_names:
|
||||
return
|
||||
self._container.update_view()
|
||||
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)
|
||||
lw = self._list_container.findChild(ListGameWidget, app_name)
|
||||
return iw, lw
|
||||
def __find_widget(self, app_name: str) -> Union[ViewWidget, None]:
|
||||
return self._container.find_widget(app_name)
|
||||
|
|
|
@ -52,7 +52,7 @@ class ProgressLabel(QLabel):
|
|||
origin_h = (image.height() - min_d) // 2
|
||||
for x, y in zip(range(origin_w, min_d), range(origin_h, min_d)):
|
||||
pixel = image.pixelColor(x, y).getRgb()
|
||||
color = list(map(lambda t: sum(t) // 2, zip(pixel[0:3], color)))
|
||||
color = list(map(lambda t: sum(t) // 2, zip(pixel[:3], color)))
|
||||
# take the V component of the HSV color
|
||||
fg_color = QColor(0, 0, 0) if QColor(*color).value() < 127 else QColor(255, 255, 255)
|
||||
bg_color = QColor(*map(lambda c: 255 - c, color))
|
||||
|
|
|
@ -1,25 +1,28 @@
|
|||
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.utils.extra_widgets import SelectViewWidget, ButtonLineEdit
|
||||
from rare.models.options import options, LibraryFilter, LibraryOrder
|
||||
from rare.utils.extra_widgets import ButtonLineEdit
|
||||
from rare.utils.misc import icon
|
||||
|
||||
|
||||
class GameListHeadBar(QWidget):
|
||||
filterChanged: pyqtSignal = pyqtSignal(str)
|
||||
goto_import: pyqtSignal = pyqtSignal()
|
||||
goto_egl_sync: pyqtSignal = pyqtSignal()
|
||||
goto_eos_ubisoft: pyqtSignal = pyqtSignal()
|
||||
filterChanged = pyqtSignal(object)
|
||||
orderChanged = pyqtSignal(object)
|
||||
viewChanged = pyqtSignal(object)
|
||||
goto_import = pyqtSignal()
|
||||
goto_egl_sync = pyqtSignal()
|
||||
goto_eos_ubisoft = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(GameListHeadBar, self).__init__(parent=parent)
|
||||
|
@ -27,43 +30,76 @@ 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")
|
||||
filters = {
|
||||
LibraryFilter.ALL: self.tr("All games"),
|
||||
LibraryFilter.INSTALLED: self.tr("Installed"),
|
||||
LibraryFilter.OFFLINE: self.tr("Offline"),
|
||||
# int(LibraryFilter.HIDDEN): self.tr("Hidden"),
|
||||
}
|
||||
for data, text in filters.items():
|
||||
self.filter.addItem(text, data)
|
||||
|
||||
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")
|
||||
self.filter.addItem(self.tr("Exclude Origin"), LibraryFilter.INSTALLABLE)
|
||||
self.filter.addItem(self.tr("Include Unreal"), LibraryFilter.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
|
||||
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)
|
||||
_filter = self.settings.value(*options.library_filter)
|
||||
if (index := self.filter.findData(_filter, Qt.UserRole)) < 0:
|
||||
raise ValueError
|
||||
else:
|
||||
self.filter.setCurrentIndex(index)
|
||||
except (TypeError, ValueError):
|
||||
self.settings.setValue(options.library_filter.key, options.library_filter.default)
|
||||
_filter = LibraryFilter(options.library_filter.default)
|
||||
self.filter.setCurrentIndex(self.filter.findData(_filter, Qt.UserRole))
|
||||
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:
|
||||
_order = LibraryOrder(self.settings.value(*options.library_order))
|
||||
if (index := self.order.findData(_order, Qt.UserRole)) < 0:
|
||||
raise ValueError
|
||||
else:
|
||||
self.order.setCurrentIndex(index)
|
||||
except (TypeError, ValueError):
|
||||
self.settings.setValue(options.library_order.key, options.library_order.default)
|
||||
_order = LibraryOrder(options.library_order.default)
|
||||
self.order.setCurrentIndex(self.order.findData(_order, Qt.UserRole))
|
||||
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)
|
||||
|
@ -71,13 +107,11 @@ class GameListHeadBar(QWidget):
|
|||
self.search_bar = ButtonLineEdit("fa.search", placeholder_text=self.tr("Search Game"))
|
||||
self.search_bar.setObjectName("SearchBar")
|
||||
self.search_bar.setFrame(False)
|
||||
self.search_bar.setMinimumWidth(200)
|
||||
|
||||
checked = QSettings().value("icon_view", True, bool)
|
||||
self.search_bar.setMinimumWidth(250)
|
||||
|
||||
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,45 +119,54 @@ 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(3)
|
||||
layout.addWidget(self.search_bar)
|
||||
layout.addStretch(2)
|
||||
layout.addStretch(4)
|
||||
layout.addWidget(self.installed_icon)
|
||||
layout.addWidget(self.installed_label)
|
||||
layout.addWidget(self.available_icon)
|
||||
layout.addWidget(self.available_label)
|
||||
layout.addStretch(2)
|
||||
layout.addWidget(self.view)
|
||||
layout.addStretch(2)
|
||||
layout.addStretch(4)
|
||||
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) -> LibraryFilter:
|
||||
return 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 = self.filter.itemData(index, Qt.UserRole)
|
||||
self.filterChanged.emit(data)
|
||||
self.settings.setValue(options.library_filter.key, int(data))
|
||||
|
||||
def current_order(self) -> LibraryOrder:
|
||||
return self.order.currentData(Qt.UserRole)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def __order_changed(self, index: int):
|
||||
data = self.order.itemData(index, Qt.UserRole)
|
||||
self.orderChanged.emit(data)
|
||||
self.settings.setValue(options.library_order.key, int(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.compat import utils as compat_utils
|
||||
from rare.widgets.elide_label import ElideLabel
|
||||
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
|
||||
|
||||
|
@ -87,11 +88,7 @@ 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, "default", str(PathSpec.egl_programdata()))
|
||||
wine_resolver.signals.result_ready.connect(self.__on_wine_resolver_result)
|
||||
QThreadPool.globalInstance().start(wine_resolver)
|
||||
|
||||
|
@ -122,14 +119,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):
|
||||
|
@ -311,7 +302,8 @@ class EGLSyncListGroup(QGroupBox):
|
|||
def items(self) -> Iterable[EGLSyncListItem]:
|
||||
# for i in range(self.list.count()):
|
||||
# yield self.list.item(i)
|
||||
return [self.ui.list.item(i) for i in range(self.ui.list.count())]
|
||||
return map(self.ui.list.item, range(self.ui.list.count()))
|
||||
# return [self.ui.list.item(i) for i in range(self.ui.list.count())]
|
||||
|
||||
|
||||
class EGLSyncExportGroup(EGLSyncListGroup):
|
||||
|
|
|
@ -224,7 +224,7 @@ class EosGroup(QGroupBox):
|
|||
|
||||
if platform.system() != "Windows":
|
||||
prefixes = config.get_prefixes()
|
||||
prefixes = {prefix for prefix in prefixes if config.prefix_exists(prefix)}
|
||||
prefixes = {prefix for prefix, _ in prefixes if config.prefix_exists(prefix)}
|
||||
if platform.system() == "Darwin":
|
||||
# TODO: add crossover support
|
||||
pass
|
||||
|
|
|
@ -263,13 +263,12 @@ class ImportGroup(QGroupBox):
|
|||
self.app_name_edit.setText(app_name)
|
||||
|
||||
def path_edit_callback(self, path) -> Tuple[bool, str, int]:
|
||||
if os.path.exists(path):
|
||||
if os.path.exists(os.path.join(path, ".egstore")):
|
||||
return True, path, IndicatorReasonsCommon.VALID
|
||||
elif os.path.basename(path) in self.__install_dirs:
|
||||
return True, path, IndicatorReasonsCommon.VALID
|
||||
else:
|
||||
if not os.path.exists(path):
|
||||
return False, path, IndicatorReasonsCommon.DIR_NOT_EXISTS
|
||||
if os.path.exists(os.path.join(path, ".egstore")):
|
||||
return True, path, IndicatorReasonsCommon.VALID
|
||||
elif os.path.basename(path) in self.__install_dirs:
|
||||
return True, path, IndicatorReasonsCommon.VALID
|
||||
return False, path, IndicatorReasonsCommon.UNDEFINED
|
||||
|
||||
@pyqtSlot(str)
|
||||
|
|
|
@ -243,15 +243,14 @@ class UbisoftGroup(QGroupBox):
|
|||
|
||||
if not uplay_games:
|
||||
self.info_label.setText(self.tr("You don't own any Ubisoft games."))
|
||||
elif activated == len(uplay_games):
|
||||
self.info_label.setText(self.tr("All your Ubisoft games have already been activated."))
|
||||
else:
|
||||
if activated == len(uplay_games):
|
||||
self.info_label.setText(self.tr("All your Ubisoft games have already been activated."))
|
||||
else:
|
||||
self.info_label.setText(
|
||||
self.tr("You have <b>{}</b> games available to redeem.").format(
|
||||
len(uplay_games) - activated
|
||||
)
|
||||
self.info_label.setText(
|
||||
self.tr("You have <b>{}</b> games available to redeem.").format(
|
||||
len(uplay_games) - activated
|
||||
)
|
||||
)
|
||||
logger.info(f"Found {len(uplay_games) - activated} game(s) to redeem.")
|
||||
|
||||
self.loading_widget.stop()
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
from rare.components.tabs.settings.widgets.linux 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 .settings import GameSettings
|
||||
from .legendary import LegendarySettings
|
||||
from .rare import RareSettings
|
||||
|
||||
|
@ -13,9 +12,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 = GameSettings(self)
|
||||
self.settings_index = self.addTab(game_settings, self.tr("Defaults"))
|
||||
|
||||
self.about = About(self)
|
||||
self.about_index = self.addTab(self.about, "About", "About")
|
||||
|
|
|
@ -16,7 +16,7 @@ def versiontuple(v):
|
|||
try:
|
||||
return tuple(map(int, (v.split("."))))
|
||||
except Exception:
|
||||
return tuple((9, 9, 9)) # It is a beta version and newer
|
||||
return 9, 9, 9
|
||||
|
||||
|
||||
class About(QWidget):
|
||||
|
@ -63,7 +63,7 @@ class About(QWidget):
|
|||
|
||||
if self.update_available:
|
||||
logger.info(f"Update available: {__version__} -> {latest_tag}")
|
||||
self.ui.update_lbl.setText("{} -> {}".format(__version__, latest_tag))
|
||||
self.ui.update_lbl.setText(f"{__version__} -> {latest_tag}")
|
||||
self.update_available_ready.emit()
|
||||
else:
|
||||
self.ui.update_lbl.setText(self.tr("You have the latest version"))
|
||||
|
|
|
@ -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)
|
|
@ -1,11 +1,13 @@
|
|||
import platform as pf
|
||||
import re
|
||||
from logging import getLogger
|
||||
from typing import Tuple, List
|
||||
from typing import Tuple, Set
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSignal, QThreadPool, QSettings
|
||||
from PyQt5.QtGui import QShowEvent, QHideEvent
|
||||
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
|
||||
|
@ -19,14 +21,11 @@ class RefreshGameMetaWorker(Worker):
|
|||
class Signals(QObject):
|
||||
finished = pyqtSignal()
|
||||
|
||||
def __init__(self, platforms: List[str], include_unreal: bool):
|
||||
def __init__(self, platforms: Set[str], include_unreal: bool):
|
||||
super(RefreshGameMetaWorker, self).__init__()
|
||||
self.signals = RefreshGameMetaWorker.Signals()
|
||||
self.core = LegendaryCoreSingleton()
|
||||
if platforms:
|
||||
self.platforms = platforms
|
||||
else:
|
||||
self.platforms = ["Windows"]
|
||||
self.platforms = platforms if platforms else {"Windows"}
|
||||
self.skip_ue = not include_unreal
|
||||
|
||||
def run_real(self) -> None:
|
||||
|
@ -37,10 +36,11 @@ class RefreshGameMetaWorker(Worker):
|
|||
self.signals.finished.emit()
|
||||
|
||||
|
||||
class LegendarySettings(QWidget, Ui_LegendarySettings):
|
||||
class LegendarySettings(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super(LegendarySettings, self).__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
self.ui = Ui_LegendarySettings()
|
||||
self.ui.setupUi(self)
|
||||
self.settings = QSettings(self)
|
||||
|
||||
self.core = LegendaryCoreSingleton()
|
||||
|
@ -53,7 +53,7 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
|
|||
file_mode=QFileDialog.DirectoryOnly,
|
||||
save_func=self.__mac_path_save,
|
||||
)
|
||||
self.install_dir_layout.addWidget(self.mac_install_dir)
|
||||
self.ui.install_dir_layout.addWidget(self.mac_install_dir)
|
||||
|
||||
# Platform-independent installation directory
|
||||
self.install_dir = PathEdit(
|
||||
|
@ -62,34 +62,34 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
|
|||
file_mode=QFileDialog.DirectoryOnly,
|
||||
save_func=self.__win_path_save,
|
||||
)
|
||||
self.install_dir_layout.addWidget(self.install_dir)
|
||||
self.ui.install_dir_layout.addWidget(self.install_dir)
|
||||
|
||||
# Max Workers
|
||||
max_workers = self.core.lgd.config["Legendary"].getint(
|
||||
"max_workers", fallback=0
|
||||
)
|
||||
self.max_worker_spin.setValue(max_workers)
|
||||
self.max_worker_spin.valueChanged.connect(self.max_worker_save)
|
||||
self.ui.max_worker_spin.setValue(max_workers)
|
||||
self.ui.max_worker_spin.valueChanged.connect(self.max_worker_save)
|
||||
# Max memory
|
||||
max_memory = self.core.lgd.config["Legendary"].getint("max_memory", fallback=0)
|
||||
self.max_memory_spin.setValue(max_memory)
|
||||
self.max_memory_spin.valueChanged.connect(self.max_memory_save)
|
||||
self.ui.max_memory_spin.setValue(max_memory)
|
||||
self.ui.max_memory_spin.valueChanged.connect(self.max_memory_save)
|
||||
# Preferred CDN
|
||||
preferred_cdn = self.core.lgd.config["Legendary"].get(
|
||||
"preferred_cdn", fallback=""
|
||||
)
|
||||
self.preferred_cdn_line.setText(preferred_cdn)
|
||||
self.preferred_cdn_line.textChanged.connect(self.preferred_cdn_save)
|
||||
self.ui.preferred_cdn_line.setText(preferred_cdn)
|
||||
self.ui.preferred_cdn_line.textChanged.connect(self.preferred_cdn_save)
|
||||
# Disable HTTPS
|
||||
disable_https = self.core.lgd.config["Legendary"].getboolean(
|
||||
"disable_https", fallback=False
|
||||
)
|
||||
self.disable_https_check.setChecked(disable_https)
|
||||
self.disable_https_check.stateChanged.connect(self.disable_https_save)
|
||||
self.ui.disable_https_check.setChecked(disable_https)
|
||||
self.ui.disable_https_check.stateChanged.connect(self.disable_https_save)
|
||||
|
||||
# Cleanup
|
||||
self.clean_button.clicked.connect(lambda: self.cleanup(False))
|
||||
self.clean_keep_manifests_button.clicked.connect(lambda: self.cleanup(True))
|
||||
self.ui.clean_button.clicked.connect(lambda: self.cleanup(False))
|
||||
self.ui.clean_keep_manifests_button.clicked.connect(lambda: self.cleanup(True))
|
||||
|
||||
self.locale_edit = IndicatorLineEdit(
|
||||
f"{self.core.language_code}-{self.core.country_code}",
|
||||
|
@ -98,51 +98,59 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
|
|||
horiz_policy=QSizePolicy.Minimum,
|
||||
parent=self,
|
||||
)
|
||||
self.locale_layout.addWidget(self.locale_edit)
|
||||
self.ui.locale_layout.addWidget(self.locale_edit)
|
||||
|
||||
self.fetch_win32_check.setChecked(self.settings.value("win32_meta", False, bool))
|
||||
self.fetch_win32_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue("win32_meta", self.fetch_win32_check.isChecked())
|
||||
self.ui.fetch_win32_check.setChecked(self.settings.value(*options.win32_meta))
|
||||
self.ui.fetch_win32_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.win32_meta.key, self.ui.fetch_win32_check.isChecked())
|
||||
)
|
||||
|
||||
self.fetch_macos_check.setChecked(self.settings.value("macos_meta", pf.system() == "Darwin", bool))
|
||||
self.fetch_macos_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue("macos_meta", self.fetch_macos_check.isChecked())
|
||||
self.ui.fetch_macos_check.setChecked(self.settings.value(*options.macos_meta))
|
||||
self.ui.fetch_macos_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.macos_meta.key, self.ui.fetch_macos_check.isChecked())
|
||||
)
|
||||
self.fetch_macos_check.setDisabled(pf.system() == "Darwin")
|
||||
self.ui.fetch_macos_check.setDisabled(pf.system() == "Darwin")
|
||||
|
||||
self.fetch_unreal_check.setChecked(self.settings.value("unreal_meta", False, bool))
|
||||
self.fetch_unreal_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue("unreal_meta", self.fetch_unreal_check.isChecked())
|
||||
self.ui.fetch_unreal_check.setChecked(self.settings.value(*options.unreal_meta))
|
||||
self.ui.fetch_unreal_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.unreal_meta.key, self.ui.fetch_unreal_check.isChecked())
|
||||
)
|
||||
|
||||
self.exclude_non_asset_check.setChecked(
|
||||
self.settings.value("exclude_non_asset", False, bool)
|
||||
)
|
||||
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)
|
||||
)
|
||||
self.exclude_entitlements_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue("exclude_entitlements", self.exclude_entitlements_check.isChecked())
|
||||
self.ui.exclude_non_asset_check.setChecked(self.settings.value(*options.exclude_non_asset))
|
||||
self.ui.exclude_non_asset_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.exclude_non_asset.key, self.ui.exclude_non_asset_check.isChecked())
|
||||
)
|
||||
|
||||
self.refresh_metadata_button.clicked.connect(self.refresh_metadata)
|
||||
self.ui.exclude_entitlements_check.setChecked(self.settings.value(*options.exclude_entitlements))
|
||||
self.ui.exclude_entitlements_check.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.exclude_entitlements.key, self.ui.exclude_entitlements_check.isChecked())
|
||||
)
|
||||
|
||||
self.ui.refresh_metadata_button.clicked.connect(self.refresh_metadata)
|
||||
# FIXME: Disable the button for now because it interferes with RareCore
|
||||
self.refresh_metadata_button.setEnabled(False)
|
||||
self.refresh_metadata_button.setVisible(False)
|
||||
self.ui.refresh_metadata_button.setEnabled(False)
|
||||
self.ui.refresh_metadata_button.setVisible(False)
|
||||
|
||||
def showEvent(self, a0: QShowEvent):
|
||||
if a0.spontaneous():
|
||||
return super().showEvent(a0)
|
||||
return super().showEvent(a0)
|
||||
|
||||
def hideEvent(self, a0: QHideEvent):
|
||||
if a0.spontaneous():
|
||||
return super().hideEvent(a0)
|
||||
self.core.lgd.save_config()
|
||||
return super().hideEvent(a0)
|
||||
|
||||
def refresh_metadata(self):
|
||||
self.refresh_metadata_button.setDisabled(True)
|
||||
platforms = []
|
||||
if self.fetch_win32_check.isChecked():
|
||||
platforms.append("Win32")
|
||||
if self.fetch_macos_check.isChecked():
|
||||
platforms.append("Mac")
|
||||
worker = RefreshGameMetaWorker(platforms, self.fetch_unreal_check.isChecked())
|
||||
worker.signals.finished.connect(lambda: self.refresh_metadata_button.setDisabled(False))
|
||||
self.ui.refresh_metadata_button.setDisabled(True)
|
||||
platforms = set()
|
||||
if self.ui.fetch_win32_check.isChecked():
|
||||
platforms.add("Win32")
|
||||
if self.ui.fetch_macos_check.isChecked():
|
||||
platforms.add("Mac")
|
||||
worker = RefreshGameMetaWorker(platforms, self.ui.fetch_unreal_check.isChecked())
|
||||
worker.signals.finished.connect(lambda: self.ui.refresh_metadata_button.setDisabled(False))
|
||||
QThreadPool.globalInstance().start(worker)
|
||||
|
||||
@staticmethod
|
||||
|
@ -162,10 +170,8 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
|
|||
if text:
|
||||
self.core.egs.language_code, self.core.egs.country_code = text.split("-")
|
||||
self.core.lgd.config.set("Legendary", "locale", text)
|
||||
else:
|
||||
if self.core.lgd.config.has_option("Legendary", "locale"):
|
||||
self.core.lgd.config.remove_option("Legendary", "locale")
|
||||
self.core.lgd.save_config()
|
||||
elif self.core.lgd.config.has_option("Legendary", "locale"):
|
||||
self.core.lgd.config.remove_option("Legendary", "locale")
|
||||
|
||||
def __mac_path_save(self, text: str) -> None:
|
||||
self.__path_save(text, "mac_install_dir")
|
||||
|
@ -180,40 +186,35 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
|
|||
if not text and option in self.core.lgd.config["Legendary"].keys():
|
||||
self.core.lgd.config["Legendary"].pop(option)
|
||||
else:
|
||||
logger.debug(f"Set %s option in config to %s", option, text)
|
||||
self.core.lgd.save_config()
|
||||
logger.debug("Set %s option in config to %s", option, text)
|
||||
|
||||
def max_worker_save(self, workers: str):
|
||||
if workers := int(workers):
|
||||
self.core.lgd.config.set("Legendary", "max_workers", str(workers))
|
||||
else:
|
||||
self.core.lgd.config.remove_option("Legendary", "max_workers")
|
||||
self.core.lgd.save_config()
|
||||
|
||||
def max_memory_save(self, memory: str):
|
||||
if memory := int(memory):
|
||||
self.core.lgd.config.set("Legendary", "max_memory", str(memory))
|
||||
else:
|
||||
self.core.lgd.config.remove_option("Legendary", "max_memory")
|
||||
self.core.lgd.save_config()
|
||||
|
||||
def preferred_cdn_save(self, cdn: str):
|
||||
if cdn:
|
||||
self.core.lgd.config.set("Legendary", "preferred_cdn", cdn.strip())
|
||||
else:
|
||||
self.core.lgd.config.remove_option("Legendary", "preferred_cdn")
|
||||
self.core.lgd.save_config()
|
||||
|
||||
def disable_https_save(self, checked: int):
|
||||
self.core.lgd.config.set(
|
||||
"Legendary", "disable_https", str(bool(checked)).lower()
|
||||
)
|
||||
self.core.lgd.save_config()
|
||||
|
||||
def cleanup(self, keep_manifests: bool):
|
||||
before = self.core.lgd.get_dir_size()
|
||||
logger.debug("Removing app metadata...")
|
||||
app_names = set(g.app_name for g in self.core.get_assets(update_assets=False))
|
||||
app_names = {g.app_name for g in self.core.get_assets(update_assets=False)}
|
||||
self.core.lgd.clean_metadata(app_names)
|
||||
|
||||
if not keep_manifests:
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import locale
|
||||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import QSettings, Qt
|
||||
from PyQt5.QtCore import QSettings, Qt, pyqtSlot, QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtWidgets import QWidget, QMessageBox
|
||||
|
||||
from rare.components.tabs.settings.widgets.rpc import RPCSettings
|
||||
from rare.models.options import options, LibraryView
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.ui.components.tabs.settings.rare import Ui_RareSettings
|
||||
from rare.utils.misc import (
|
||||
|
@ -22,116 +22,117 @@ from rare.utils.paths import create_desktop_link, desktop_link_path, log_dir, de
|
|||
|
||||
logger = getLogger("RareSettings")
|
||||
|
||||
languages = [("en", "English"),
|
||||
("de", "Deutsch"),
|
||||
("fr", "Français"),
|
||||
("zh-Hans", "Simplified Chinese"),
|
||||
("zh_TW", "Chinese Taiwan"),
|
||||
("pt_BR", "Portuguese (Brazil)"),
|
||||
("ca", "Catalan"),
|
||||
("ru", "Russian"),
|
||||
("tr", "Turkish"),
|
||||
("uk", "Ukrainian")]
|
||||
|
||||
|
||||
class RareSettings(QWidget, Ui_RareSettings):
|
||||
class RareSettings(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super(RareSettings, self).__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
self.ui = Ui_RareSettings()
|
||||
self.ui.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()
|
||||
language = self.settings.value("language", self.core.language_code, type=str)
|
||||
self.settings = QSettings(self)
|
||||
|
||||
# Select lang
|
||||
self.lang_select.addItems([i[1] for i in languages])
|
||||
if language in get_translations():
|
||||
index = [lang[0] for lang in languages].index(language)
|
||||
self.lang_select.setCurrentIndex(index)
|
||||
self.ui.lang_select.addItem(self.tr("System default"), options.language.default)
|
||||
for lang_code, title in get_translations():
|
||||
self.ui.lang_select.addItem(title, lang_code)
|
||||
language = self.settings.value(*options.language)
|
||||
if (index := self.ui.lang_select.findData(language, Qt.UserRole)) > 0:
|
||||
self.ui.lang_select.setCurrentIndex(index)
|
||||
else:
|
||||
self.lang_select.setCurrentIndex(0)
|
||||
self.lang_select.currentIndexChanged.connect(self.update_lang)
|
||||
self.ui.lang_select.setCurrentIndex(0)
|
||||
self.ui.lang_select.currentIndexChanged.connect(self.on_lang_changed)
|
||||
|
||||
colors = get_color_schemes()
|
||||
self.color_select.addItems(colors)
|
||||
if (color := self.settings.value("color_scheme")) in colors:
|
||||
self.color_select.setCurrentIndex(self.color_select.findText(color))
|
||||
self.color_select.setDisabled(False)
|
||||
self.style_select.setDisabled(True)
|
||||
self.ui.color_select.addItem(self.tr("None"), "")
|
||||
for item in get_color_schemes():
|
||||
self.ui.color_select.addItem(item, item)
|
||||
color = self.settings.value(*options.color_scheme)
|
||||
if (index := self.ui.color_select.findData(color, Qt.UserRole)) > 0:
|
||||
self.ui.color_select.setCurrentIndex(index)
|
||||
self.ui.color_select.setDisabled(False)
|
||||
self.ui.style_select.setDisabled(True)
|
||||
else:
|
||||
self.color_select.setCurrentIndex(0)
|
||||
self.color_select.currentIndexChanged.connect(self.on_color_select_changed)
|
||||
self.ui.color_select.setCurrentIndex(0)
|
||||
self.ui.color_select.currentIndexChanged.connect(self.on_color_select_changed)
|
||||
|
||||
styles = get_style_sheets()
|
||||
self.style_select.addItems(styles)
|
||||
if (style := self.settings.value("style_sheet")) in styles:
|
||||
self.style_select.setCurrentIndex(self.style_select.findText(style))
|
||||
self.style_select.setDisabled(False)
|
||||
self.color_select.setDisabled(True)
|
||||
self.ui.style_select.addItem(self.tr("None"), "")
|
||||
for item in get_style_sheets():
|
||||
self.ui.style_select.addItem(item, item)
|
||||
style = self.settings.value(*options.style_sheet)
|
||||
if (index := self.ui.style_select.findData(style, Qt.UserRole)) > 0:
|
||||
self.ui.style_select.setCurrentIndex(index)
|
||||
self.ui.style_select.setDisabled(False)
|
||||
self.ui.color_select.setDisabled(True)
|
||||
else:
|
||||
self.style_select.setCurrentIndex(0)
|
||||
self.style_select.currentIndexChanged.connect(self.on_style_select_changed)
|
||||
self.ui.style_select.setCurrentIndex(0)
|
||||
self.ui.style_select.currentIndexChanged.connect(self.on_style_select_changed)
|
||||
|
||||
self.ui.view_combo.addItem(self.tr("Game covers"), LibraryView.COVER)
|
||||
self.ui.view_combo.addItem(self.tr("Vertical list"), LibraryView.VLIST)
|
||||
view = LibraryView(self.settings.value(*options.library_view))
|
||||
if (index := self.ui.view_combo.findData(view)) > -1:
|
||||
self.ui.view_combo.setCurrentIndex(index)
|
||||
else:
|
||||
self.ui.view_combo.setCurrentIndex(0)
|
||||
self.ui.view_combo.currentIndexChanged.connect(self.on_view_combo_changed)
|
||||
|
||||
self.rpc = RPCSettings(self)
|
||||
self.right_layout.insertWidget(1, self.rpc, alignment=Qt.AlignTop)
|
||||
self.ui.right_layout.insertWidget(1, self.rpc, alignment=Qt.AlignTop)
|
||||
|
||||
self.init_checkboxes(self.checkboxes)
|
||||
self.sys_tray.stateChanged.connect(
|
||||
lambda: self.settings.setValue("sys_tray", self.sys_tray.isChecked())
|
||||
self.ui.sys_tray.setChecked(self.settings.value(*options.sys_tray))
|
||||
self.ui.sys_tray.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.sys_tray.key, self.ui.sys_tray.isChecked())
|
||||
)
|
||||
self.auto_update.stateChanged.connect(
|
||||
lambda: self.settings.setValue("auto_update", self.auto_update.isChecked())
|
||||
|
||||
self.ui.auto_update.setChecked(self.settings.value(*options.auto_update))
|
||||
self.ui.auto_update.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.auto_update.key, self.ui.auto_update.isChecked())
|
||||
)
|
||||
self.confirm_start.stateChanged.connect(
|
||||
lambda: self.settings.setValue(
|
||||
"confirm_start", self.confirm_start.isChecked()
|
||||
)
|
||||
|
||||
self.ui.confirm_start.setChecked(self.settings.value(*options.confirm_start))
|
||||
self.ui.confirm_start.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.confirm_start.key, self.ui.confirm_start.isChecked())
|
||||
)
|
||||
self.auto_sync_cloud.stateChanged.connect(
|
||||
lambda: self.settings.setValue(
|
||||
"auto_sync_cloud", self.auto_sync_cloud.isChecked()
|
||||
)
|
||||
|
||||
self.ui.auto_sync_cloud.setChecked(self.settings.value(*options.auto_sync_cloud))
|
||||
self.ui.auto_sync_cloud.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.auto_sync_cloud.key, self.ui.auto_sync_cloud.isChecked())
|
||||
)
|
||||
self.notification.stateChanged.connect(
|
||||
lambda: self.settings.setValue("notification", self.notification.isChecked())
|
||||
|
||||
self.ui.notification.setChecked(self.settings.value(*options.notification))
|
||||
self.ui.notification.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.notification.key, self.ui.notification.isChecked())
|
||||
)
|
||||
self.save_size.stateChanged.connect(self.save_window_size)
|
||||
self.log_games.stateChanged.connect(
|
||||
lambda: self.settings.setValue("show_console", self.log_games.isChecked())
|
||||
|
||||
self.ui.save_size.setChecked(self.settings.value(*options.save_size))
|
||||
self.ui.save_size.stateChanged.connect(self.save_window_size)
|
||||
|
||||
self.ui.log_games.setChecked(self.settings.value(*options.log_games))
|
||||
self.ui.log_games.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.log_games.key, self.ui.log_games.isChecked())
|
||||
)
|
||||
|
||||
if desktop_links_supported():
|
||||
self.desktop_link = desktop_link_path("Rare", "desktop")
|
||||
self.start_menu_link = desktop_link_path("Rare", "start_menu")
|
||||
else:
|
||||
self.desktop_link_btn.setToolTip(self.tr("Not supported"))
|
||||
self.desktop_link_btn.setDisabled(True)
|
||||
self.startmenu_link_btn.setToolTip(self.tr("Not supported"))
|
||||
self.startmenu_link_btn.setDisabled(True)
|
||||
self.ui.desktop_link_btn.setToolTip(self.tr("Not supported"))
|
||||
self.ui.desktop_link_btn.setDisabled(True)
|
||||
self.ui.startmenu_link_btn.setToolTip(self.tr("Not supported"))
|
||||
self.ui.startmenu_link_btn.setDisabled(True)
|
||||
self.desktop_link = ""
|
||||
self.start_menu_link = ""
|
||||
|
||||
if self.desktop_link and self.desktop_link.exists():
|
||||
self.desktop_link_btn.setText(self.tr("Remove desktop link"))
|
||||
self.ui.desktop_link_btn.setText(self.tr("Remove desktop link"))
|
||||
|
||||
if self.start_menu_link and self.start_menu_link.exists():
|
||||
self.startmenu_link_btn.setText(self.tr("Remove start menu link"))
|
||||
self.ui.startmenu_link_btn.setText(self.tr("Remove start menu link"))
|
||||
|
||||
self.desktop_link_btn.clicked.connect(self.create_desktop_link)
|
||||
self.startmenu_link_btn.clicked.connect(self.create_start_menu_link)
|
||||
self.ui.desktop_link_btn.clicked.connect(self.create_desktop_link)
|
||||
self.ui.startmenu_link_btn.clicked.connect(self.create_start_menu_link)
|
||||
|
||||
self.log_dir_open_button.clicked.connect(self.open_dir)
|
||||
self.log_dir_clean_button.clicked.connect(self.clean_logdir)
|
||||
self.ui.log_dir_open_button.clicked.connect(self.open_directory)
|
||||
self.ui.log_dir_clean_button.clicked.connect(self.clean_logdir)
|
||||
|
||||
# get size of logdir
|
||||
size = sum(
|
||||
|
@ -139,10 +140,11 @@ class RareSettings(QWidget, Ui_RareSettings):
|
|||
for f in log_dir().iterdir()
|
||||
if log_dir().joinpath(f).is_file()
|
||||
)
|
||||
self.log_dir_size_label.setText(format_size(size))
|
||||
self.ui.log_dir_size_label.setText(format_size(size))
|
||||
# self.log_dir_clean_button.setVisible(False)
|
||||
# self.log_dir_size_label.setVisible(False)
|
||||
|
||||
@pyqtSlot()
|
||||
def clean_logdir(self):
|
||||
for f in log_dir().iterdir():
|
||||
try:
|
||||
|
@ -155,17 +157,18 @@ class RareSettings(QWidget, Ui_RareSettings):
|
|||
for f in log_dir().iterdir()
|
||||
if log_dir().joinpath(f).is_file()
|
||||
)
|
||||
self.log_dir_size_label.setText(format_size(size))
|
||||
self.ui.log_dir_size_label.setText(format_size(size))
|
||||
|
||||
@pyqtSlot()
|
||||
def create_start_menu_link(self):
|
||||
try:
|
||||
if not os.path.exists(self.start_menu_link):
|
||||
if not create_desktop_link(app_name="rare_shortcut", link_type="start_menu"):
|
||||
return
|
||||
self.startmenu_link_btn.setText(self.tr("Remove start menu link"))
|
||||
self.ui.startmenu_link_btn.setText(self.tr("Remove start menu link"))
|
||||
else:
|
||||
os.remove(self.start_menu_link)
|
||||
self.startmenu_link_btn.setText(self.tr("Create start menu link"))
|
||||
self.ui.startmenu_link_btn.setText(self.tr("Create start menu link"))
|
||||
except PermissionError as e:
|
||||
logger.error(str(e))
|
||||
QMessageBox.warning(
|
||||
|
@ -174,15 +177,16 @@ class RareSettings(QWidget, Ui_RareSettings):
|
|||
self.tr("Permission error, cannot remove {}").format(self.start_menu_link),
|
||||
)
|
||||
|
||||
@pyqtSlot()
|
||||
def create_desktop_link(self):
|
||||
try:
|
||||
if not os.path.exists(self.desktop_link):
|
||||
if not create_desktop_link(app_name="rare_shortcut", link_type="desktop"):
|
||||
return
|
||||
self.desktop_link_btn.setText(self.tr("Remove Desktop link"))
|
||||
self.ui.desktop_link_btn.setText(self.tr("Remove Desktop link"))
|
||||
else:
|
||||
os.remove(self.desktop_link)
|
||||
self.desktop_link_btn.setText(self.tr("Create desktop link"))
|
||||
self.ui.desktop_link_btn.setText(self.tr("Create desktop link"))
|
||||
except PermissionError as e:
|
||||
logger.error(str(e))
|
||||
logger.warning(
|
||||
|
@ -191,43 +195,46 @@ class RareSettings(QWidget, Ui_RareSettings):
|
|||
self.tr("Permission error, cannot remove {}").format(self.start_menu_link),
|
||||
)
|
||||
|
||||
def on_color_select_changed(self, scheme):
|
||||
@pyqtSlot(int)
|
||||
def on_color_select_changed(self, index: int):
|
||||
scheme = self.ui.color_select.itemData(index, Qt.UserRole)
|
||||
if scheme:
|
||||
self.style_select.setCurrentIndex(0)
|
||||
self.style_select.setDisabled(True)
|
||||
self.settings.setValue("color_scheme", self.color_select.currentText())
|
||||
set_color_pallete(self.color_select.currentText())
|
||||
self.ui.style_select.setCurrentIndex(0)
|
||||
self.ui.style_select.setDisabled(True)
|
||||
else:
|
||||
self.settings.setValue("color_scheme", "")
|
||||
self.style_select.setDisabled(False)
|
||||
set_color_pallete("")
|
||||
self.ui.style_select.setDisabled(False)
|
||||
self.settings.setValue("color_scheme", scheme)
|
||||
set_color_pallete(scheme)
|
||||
|
||||
def on_style_select_changed(self, style):
|
||||
@pyqtSlot(int)
|
||||
def on_style_select_changed(self, index: int):
|
||||
style = self.ui.style_select.itemData(index, Qt.UserRole)
|
||||
if style:
|
||||
self.color_select.setCurrentIndex(0)
|
||||
self.color_select.setDisabled(True)
|
||||
self.settings.setValue("style_sheet", self.style_select.currentText())
|
||||
set_style_sheet(self.style_select.currentText())
|
||||
self.ui.color_select.setCurrentIndex(0)
|
||||
self.ui.color_select.setDisabled(True)
|
||||
else:
|
||||
self.settings.setValue("style_sheet", "")
|
||||
self.color_select.setDisabled(False)
|
||||
set_style_sheet("")
|
||||
self.ui.color_select.setDisabled(False)
|
||||
self.settings.setValue("style_sheet", style)
|
||||
set_style_sheet(style)
|
||||
|
||||
def open_dir(self):
|
||||
if platform.system() == "Windows":
|
||||
os.startfile(log_dir()) # pylint: disable=E1101
|
||||
else:
|
||||
opener = "open" if sys.platform == "darwin" else "xdg-open"
|
||||
subprocess.Popen([opener, log_dir()])
|
||||
@pyqtSlot(int)
|
||||
def on_view_combo_changed(self, index: int):
|
||||
view = LibraryView(self.ui.view_combo.itemData(index, Qt.UserRole))
|
||||
self.settings.setValue(options.library_view.key, int(view))
|
||||
|
||||
@pyqtSlot()
|
||||
def open_directory(self):
|
||||
QDesktopServices.openUrl(QUrl(f"file://{log_dir()}"))
|
||||
|
||||
@pyqtSlot()
|
||||
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.ui.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))
|
||||
@pyqtSlot(int)
|
||||
def on_lang_changed(self, index: int):
|
||||
lang_code = self.ui.lang_select.itemData(index, Qt.UserRole)
|
||||
if lang_code == locale.getlocale()[0]:
|
||||
self.settings.remove(options.language.key)
|
||||
else:
|
||||
self.settings.setValue(options.language.key, lang_code)
|
||||
|
|
43
rare/components/tabs/settings/settings.py
Normal file
43
rare/components/tabs/settings/settings.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
import platform as pf
|
||||
from logging import getLogger
|
||||
|
||||
from .widgets.env_vars import EnvVars
|
||||
from .widgets.game import GameSettingsBase
|
||||
from .widgets.launch import LaunchSettingsBase
|
||||
from .widgets.overlay import DxvkSettings
|
||||
from .widgets.wrappers import WrapperSettings
|
||||
|
||||
if pf.system() != "Windows":
|
||||
from .widgets.wine import WineSettings
|
||||
|
||||
if pf.system() in {"Linux", "FreeBSD"}:
|
||||
from .widgets.proton import ProtonSettings
|
||||
from .widgets.overlay import MangoHudSettings
|
||||
|
||||
logger = getLogger("GameSettings")
|
||||
|
||||
|
||||
class LaunchSettings(LaunchSettingsBase):
|
||||
def __init__(self, parent=None):
|
||||
super(LaunchSettings, self).__init__(WrapperSettings, parent=parent)
|
||||
|
||||
|
||||
class GameSettings(GameSettingsBase):
|
||||
def __init__(self, parent=None):
|
||||
if pf.system() in {"Linux", "FreeBSD"}:
|
||||
super(GameSettings, self).__init__(
|
||||
LaunchSettings, DxvkSettings, EnvVars,
|
||||
WineSettings, ProtonSettings, MangoHudSettings,
|
||||
parent=parent
|
||||
)
|
||||
elif pf.system() != "Windows":
|
||||
super(GameSettings, self).__init__(
|
||||
LaunchSettings, DxvkSettings, EnvVars,
|
||||
WineSettings,
|
||||
parent=parent
|
||||
)
|
||||
else:
|
||||
super(GameSettings, self).__init__(
|
||||
LaunchSettings, DxvkSettings, EnvVars,
|
||||
parent=parent
|
||||
)
|
|
@ -1,26 +0,0 @@
|
|||
from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from .overlay_settings import OverlaySettings, CustomOption
|
||||
|
||||
|
||||
class DxvkSettings(OverlaySettings):
|
||||
def __init__(self):
|
||||
super(DxvkSettings, self).__init__(
|
||||
[
|
||||
("fps", QCoreApplication.translate("DxvkSettings", "FPS")),
|
||||
("frametime", QCoreApplication.translate("DxvkSettings", "Frametime")),
|
||||
("memory", QCoreApplication.translate("DxvkSettings", "Memory usage")),
|
||||
("gpuload", QCoreApplication.translate("DxvkSettings", "GPU usage")),
|
||||
("devinfo", QCoreApplication.translate("DxvkSettings", "Show Device info")),
|
||||
("version", QCoreApplication.translate("DxvkSettings", "DXVK Version")),
|
||||
("api", QCoreApplication.translate("DxvkSettings", "D3D feature level")),
|
||||
("compiler", QCoreApplication.translate("DxvkSettings", "Compiler activity")),
|
||||
],
|
||||
[
|
||||
(CustomOption.number_input("scale", 1, True), QCoreApplication.translate("DxvkSettings", "Scale"))
|
||||
],
|
||||
"DXVK_HUD", "0"
|
||||
)
|
||||
|
||||
self.setTitle(self.tr("DXVK Settings"))
|
||||
self.gb_options.setTitle(self.tr("Custom options"))
|
|
@ -1,6 +1,7 @@
|
|||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import QFileSystemWatcher, Qt
|
||||
from PyQt5.QtGui import QShowEvent
|
||||
from PyQt5.QtWidgets import (
|
||||
QGroupBox,
|
||||
QHeaderView,
|
||||
|
@ -20,6 +21,7 @@ class EnvVars(QGroupBox):
|
|||
self.setTitle(self.tr("Environment variables"))
|
||||
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.app_name: str = "default"
|
||||
|
||||
self.table_model = EnvVarsTableModel(self.core)
|
||||
self.table_view = QTableView(self)
|
||||
|
@ -44,8 +46,14 @@ class EnvVars(QGroupBox):
|
|||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(self.table_view)
|
||||
|
||||
def keyPressEvent(self, e):
|
||||
if e.key() in {Qt.Key_Delete, Qt.Key_Backspace}:
|
||||
def showEvent(self, a0: QShowEvent):
|
||||
if a0.spontaneous():
|
||||
return super().showEvent(a0)
|
||||
self.table_model.load(self.app_name)
|
||||
return super().showEvent(a0)
|
||||
|
||||
def keyPressEvent(self, a0):
|
||||
if a0.key() in {Qt.Key_Delete, Qt.Key_Backspace}:
|
||||
indexes = self.table_view.selectedIndexes()
|
||||
if not len(indexes):
|
||||
return
|
||||
|
@ -54,11 +62,8 @@ class EnvVars(QGroupBox):
|
|||
self.table_view.model().removeRow(idx.row())
|
||||
elif idx.column() == 1:
|
||||
self.table_view.model().setData(idx, "", Qt.EditRole)
|
||||
elif e.key() == Qt.Key_Escape:
|
||||
e.ignore()
|
||||
elif a0.key() == Qt.Key_Escape:
|
||||
a0.ignore()
|
||||
|
||||
def reset_model(self):
|
||||
self.table_model.reset()
|
||||
|
||||
def update_game(self, app_name):
|
||||
self.table_model.load(app_name)
|
||||
|
|
|
@ -11,12 +11,14 @@ from rare.lgndr.core import LegendaryCore
|
|||
from rare.utils.misc import icon
|
||||
|
||||
if platform.system() != "Windows":
|
||||
if platform.system() != "Darwin":
|
||||
from rare.utils import proton
|
||||
from rare.utils.compat.wine import get_wine_environment
|
||||
|
||||
if platform.system() in {"Linux", "FreeBSD"}:
|
||||
from rare.utils.compat.steam import get_steam_environment
|
||||
|
||||
|
||||
class EnvVarsTableModel(QAbstractTableModel):
|
||||
def __init__(self, core: LegendaryCore, parent = None):
|
||||
def __init__(self, core: LegendaryCore, parent=None):
|
||||
super(EnvVarsTableModel, self).__init__(parent=parent)
|
||||
self.core = core
|
||||
|
||||
|
@ -26,15 +28,15 @@ class EnvVarsTableModel(QAbstractTableModel):
|
|||
self.__validator = re.compile(r"(^[A-Za-z_][A-Za-z0-9_]*)")
|
||||
self.__data_map: ChainMap = ChainMap()
|
||||
|
||||
self.__readonly = [
|
||||
"STEAM_COMPAT_DATA_PATH",
|
||||
"WINEPREFIX",
|
||||
self.__readonly = {
|
||||
"DXVK_HUD",
|
||||
"MANGOHUD",
|
||||
"MANGOHUD_CONFIG",
|
||||
]
|
||||
}
|
||||
if platform.system() != "Windows":
|
||||
if platform.system() != "Darwin":
|
||||
self.__readonly.extend(proton.get_steam_environment(None).keys())
|
||||
self.__readonly.update(get_wine_environment().keys())
|
||||
if platform.system() in {"Linux", "FreeBSD"}:
|
||||
self.__readonly.update(get_steam_environment().keys())
|
||||
|
||||
self.__default: str = "default"
|
||||
self.__appname: str = None
|
||||
|
@ -256,8 +258,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
|
||||
|
||||
|
|
81
rare/components/tabs/settings/widgets/game.py
Normal file
81
rare/components/tabs/settings/widgets/game.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
import platform as pf
|
||||
from typing import Type
|
||||
|
||||
from PyQt5.QtCore import QSettings, Qt
|
||||
from PyQt5.QtGui import QHideEvent
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout
|
||||
)
|
||||
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.utils import config_helper as config
|
||||
from rare.widgets.side_tab import SideTabContents
|
||||
from .env_vars import EnvVars
|
||||
from .launch import LaunchSettingsType
|
||||
from .overlay import DxvkSettings
|
||||
|
||||
if pf.system() != "Windows":
|
||||
from .wine import WineSettings
|
||||
|
||||
if pf.system() in {"Linux", "FreeBSD"}:
|
||||
from .proton import ProtonSettings
|
||||
from .overlay import MangoHudSettings
|
||||
|
||||
|
||||
class GameSettingsBase(QWidget, SideTabContents):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
launch_widget: Type[LaunchSettingsType],
|
||||
dxvk_widget: Type[DxvkSettings],
|
||||
envvar_widget: Type[EnvVars],
|
||||
wine_widget: Type['WineSettings'] = None,
|
||||
proton_widget: Type['ProtonSettings'] = None,
|
||||
mangohud_widget: Type['MangoHudSettings'] = None,
|
||||
parent=None
|
||||
):
|
||||
super(GameSettingsBase, self).__init__(parent=parent)
|
||||
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.settings = QSettings(self)
|
||||
self.app_name: str = "default"
|
||||
|
||||
self.launch = launch_widget(self)
|
||||
self.env_vars = envvar_widget(self)
|
||||
|
||||
if pf.system() != "Windows":
|
||||
self.wine = wine_widget(self)
|
||||
self.wine.environ_changed.connect(self.env_vars.reset_model)
|
||||
|
||||
if pf.system() in {"Linux", "FreeBSD"}:
|
||||
self.proton_tool = proton_widget(self)
|
||||
self.proton_tool.environ_changed.connect(self.env_vars.reset_model)
|
||||
self.proton_tool.tool_enabled.connect(self.wine.tool_enabled)
|
||||
self.proton_tool.tool_enabled.connect(self.launch.tool_enabled)
|
||||
|
||||
self.dxvk = dxvk_widget(self)
|
||||
self.dxvk.environ_changed.connect(self.env_vars.reset_model)
|
||||
|
||||
if pf.system() in {"Linux", "FreeBSD"}:
|
||||
self.mangohud = mangohud_widget(self)
|
||||
self.mangohud.environ_changed.connect(self.env_vars.reset_model)
|
||||
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
self.main_layout.addWidget(self.launch)
|
||||
if pf.system() != "Windows":
|
||||
self.main_layout.addWidget(self.wine)
|
||||
if pf.system() in {"Linux", "FreeBSD"}:
|
||||
self.main_layout.addWidget(self.proton_tool)
|
||||
self.main_layout.addWidget(self.dxvk)
|
||||
if pf.system() in {"Linux", "FreeBSD"}:
|
||||
self.main_layout.addWidget(self.mangohud)
|
||||
self.main_layout.addWidget(self.env_vars)
|
||||
|
||||
self.main_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
def hideEvent(self, a0: QHideEvent):
|
||||
if a0.spontaneous():
|
||||
return super().hideEvent(a0)
|
||||
config.save_config()
|
||||
return super().hideEvent(a0)
|
91
rare/components/tabs/settings/widgets/launch.py
Normal file
91
rare/components/tabs/settings/widgets/launch.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
import os
|
||||
import shutil
|
||||
from typing import Tuple, Type, TypeVar
|
||||
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QShowEvent
|
||||
from PyQt5.QtWidgets import QCheckBox, QFileDialog, QFormLayout, QVBoxLayout, QGroupBox
|
||||
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
import rare.utils.config_helper as config
|
||||
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
|
||||
from .wrappers import WrapperSettings
|
||||
|
||||
|
||||
class LaunchSettingsBase(QGroupBox):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
wrapper_widget: Type[WrapperSettings],
|
||||
parent=None
|
||||
):
|
||||
super(LaunchSettingsBase, self).__init__(parent=parent)
|
||||
self.setTitle(self.tr("Launch settings"))
|
||||
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.app_name: str = "default"
|
||||
|
||||
self.prelaunch_edit = PathEdit(
|
||||
path="",
|
||||
placeholder=self.tr("Path to script or program to run before the game launches"),
|
||||
file_mode=QFileDialog.ExistingFile,
|
||||
edit_func=self.__prelaunch_edit_callback,
|
||||
save_func=self.__prelaunch_save_callback,
|
||||
)
|
||||
|
||||
self.wrappers_widget = wrapper_widget(self)
|
||||
|
||||
self.prelaunch_check = QCheckBox(self.tr("Wait for command to finish before starting the game"))
|
||||
font = self.font()
|
||||
font.setItalic(True)
|
||||
self.prelaunch_check.setFont(font)
|
||||
self.prelaunch_check.stateChanged.connect(self.__prelauch_check_changed)
|
||||
|
||||
prelaunch_layout = QVBoxLayout()
|
||||
prelaunch_layout.addWidget(self.prelaunch_edit)
|
||||
prelaunch_layout.addWidget(self.prelaunch_check)
|
||||
|
||||
self.main_layout = QFormLayout(self)
|
||||
self.main_layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
|
||||
self.main_layout.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
self.main_layout.setFormAlignment(Qt.AlignLeading | Qt.AlignTop)
|
||||
|
||||
self.main_layout.addRow(self.tr("Wrappers"), self.wrappers_widget)
|
||||
self.main_layout.addRow(self.tr("Prelaunch"), prelaunch_layout)
|
||||
|
||||
def showEvent(self, a0: QShowEvent):
|
||||
if a0.spontaneous():
|
||||
return super().showEvent(a0)
|
||||
command = config.get_option(self.app_name, "pre_launch_command", fallback="")
|
||||
wait = config.get_boolean(self.app_name, "pre_launch_wait", fallback=False)
|
||||
|
||||
self.prelaunch_edit.setText(command)
|
||||
self.prelaunch_check.setChecked(wait)
|
||||
self.prelaunch_check.setEnabled(bool(command))
|
||||
|
||||
return super().showEvent(a0)
|
||||
|
||||
@pyqtSlot()
|
||||
def tool_enabled(self):
|
||||
self.wrappers_widget.update_state()
|
||||
|
||||
@staticmethod
|
||||
def __prelaunch_edit_callback(text: str) -> Tuple[bool, str, int]:
|
||||
if not text.strip():
|
||||
return True, text, IndicatorReasonsCommon.VALID
|
||||
if not os.path.isfile(text.split()[0]) and not shutil.which(text.split()[0]):
|
||||
return False, text, IndicatorReasonsCommon.FILE_NOT_EXISTS
|
||||
else:
|
||||
return True, text, IndicatorReasonsCommon.VALID
|
||||
|
||||
def __prelaunch_save_callback(self, text):
|
||||
config.save_option(self.app_name, "pre_launch_command", text)
|
||||
self.prelaunch_check.setEnabled(bool(text))
|
||||
if not text:
|
||||
config.remove_option(self.app_name, "pre_launch_wait")
|
||||
|
||||
def __prelauch_check_changed(self):
|
||||
config.set_boolean(self.app_name, "pre_launch_wait", self.prelaunch_check.isChecked())
|
||||
|
||||
|
||||
LaunchSettingsType = TypeVar("LaunchSettingsType", bound=LaunchSettingsBase)
|
|
@ -1,88 +0,0 @@
|
|||
import os
|
||||
import shutil
|
||||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5.QtWidgets import QFileDialog, QWidget
|
||||
|
||||
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.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
|
||||
from rare.utils import config_helper
|
||||
|
||||
logger = getLogger("LinuxSettings")
|
||||
|
||||
|
||||
class LinuxSettings(QWidget):
|
||||
# str: option key
|
||||
environ_changed = pyqtSignal(str)
|
||||
|
||||
def __init__(self, name=None, parent=None):
|
||||
super(LinuxSettings, self).__init__(parent=parent)
|
||||
self.ui = Ui_LinuxSettings()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.signals = GlobalSignalsSingleton()
|
||||
|
||||
self.name = name if name is not None else "default"
|
||||
|
||||
# Wine prefix
|
||||
self.wine_prefix = PathEdit(
|
||||
self.load_prefix(),
|
||||
file_mode=QFileDialog.DirectoryOnly,
|
||||
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)
|
||||
|
||||
# Wine executable
|
||||
self.wine_exec = PathEdit(
|
||||
self.load_setting(self.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"
|
||||
),
|
||||
)
|
||||
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)
|
||||
|
||||
|
||||
def load_prefix(self) -> str:
|
||||
return self.load_setting(
|
||||
f"{self.name}.env",
|
||||
"WINEPREFIX",
|
||||
fallback=self.load_setting(self.name, "wine_prefix"),
|
||||
)
|
||||
|
||||
def save_prefix(self, text: str):
|
||||
self.save_setting(text, f"{self.name}.env", "WINEPREFIX")
|
||||
self.environ_changed.emit("WINEPREFIX")
|
||||
self.save_setting(text, self.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):
|
||||
if text:
|
||||
config_helper.add_option(section, setting, text)
|
||||
logger.debug(f"Set {setting} in {f'[{section}]'} to {text}")
|
||||
else:
|
||||
config_helper.remove_option(section, setting)
|
||||
logger.debug(f"Unset {setting} from {f'[{section}]'}")
|
||||
config_helper.save_config()
|
|
@ -1,108 +0,0 @@
|
|||
import shutil
|
||||
from enum import Enum
|
||||
|
||||
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
|
||||
|
||||
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):
|
||||
super(MangoHudSettings, self).__init__(
|
||||
[
|
||||
("fps", QCoreApplication.translate("MangoSettings", "FPS")),
|
||||
("frame_timing", QCoreApplication.translate("MangoSettings", "Frame Time")),
|
||||
("cpu_stats", QCoreApplication.translate("MangoSettings", "CPU Load")),
|
||||
("gpu_stats", QCoreApplication.translate("MangoSettings", "GPU Load")),
|
||||
("cpu_temp", QCoreApplication.translate("MangoSettings", "CPU Temp")),
|
||||
("gpu_temp", QCoreApplication.translate("MangoSettings", "GPU Temp")),
|
||||
("ram", QCoreApplication.translate("MangoSettings", "Memory usage")),
|
||||
("vram", QCoreApplication.translate("MangoSettings", "VRAM usage")),
|
||||
("time", QCoreApplication.translate("MangoSettings", "Local Time")),
|
||||
("version", QCoreApplication.translate("MangoSettings", "MangoHud Version")),
|
||||
("arch", QCoreApplication.translate("MangoSettings", "System architecture")),
|
||||
("histogram", QCoreApplication.translate("MangoSettings", "FPS Graph")),
|
||||
("gpu_name", QCoreApplication.translate("MangoSettings", "GPU Name")),
|
||||
("cpu_power", QCoreApplication.translate("MangoSettings", "CPU Power consumption")),
|
||||
("gpu_power", QCoreApplication.translate("MangoSettings", "GPU Power consumption")),
|
||||
],
|
||||
[
|
||||
(
|
||||
CustomOption.number_input("font_size", 24, is_float=False),
|
||||
QCoreApplication.translate("MangoSettings", "Font size")
|
||||
),
|
||||
(
|
||||
CustomOption.select_input("position", position_values),
|
||||
QCoreApplication.translate("MangoSettings", "Position")
|
||||
)
|
||||
],
|
||||
"MANGOHUD_CONFIG", "no_display", set_activation_state=self.set_activation_state
|
||||
)
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.setTitle(self.tr("MangoHud Settings"))
|
||||
self.gb_options.setTitle(self.tr("Custom options"))
|
||||
|
||||
def load_settings(self, name: str):
|
||||
self.settings_updatable = False
|
||||
self.name = name
|
||||
# override
|
||||
cfg = self.core.lgd.config.get(f"{name}.env", "MANGOHUD_CONFIG", fallback="")
|
||||
activated = "mangohud" in self.core.lgd.config.get(name, "wrapper", fallback="")
|
||||
if not activated:
|
||||
self.settings_updatable = False
|
||||
self.gb_options.setDisabled(True)
|
||||
for i, checkbox in enumerate(list(self.checkboxes.values())):
|
||||
checkbox.setChecked(i < 4)
|
||||
self.show_overlay_combo.setCurrentIndex(0)
|
||||
self.settings_updatable = True
|
||||
return
|
||||
super(MangoHudSettings, self).load_settings(name)
|
||||
self.settings_updatable = False
|
||||
self.show_overlay_combo.setCurrentIndex(2)
|
||||
self.gb_options.setDisabled(False)
|
||||
for var_name, checkbox in list(self.checkboxes.items())[:4]:
|
||||
checkbox.setChecked(f"{var_name}=0" not in cfg)
|
||||
self.settings_updatable = True
|
||||
|
||||
def set_activation_state(self, state: Enum): # pylint: disable=E0202
|
||||
if state in [ActivationStates.DEFAULT, ActivationStates.HIDDEN]:
|
||||
self.set_wrapper_activated.emit(False)
|
||||
self.gb_options.setDisabled(True)
|
||||
|
||||
elif state == ActivationStates.ACTIVATED:
|
||||
if not shutil.which("mangohud"):
|
||||
self.show_overlay_combo.setCurrentIndex(0)
|
||||
QMessageBox.warning(self, "Error", self.tr("Mangohud is not installed or not in path"))
|
||||
return
|
||||
|
||||
cfg = self.core.lgd.config.get(f"{self.name}.env", "MANGOHUD_CONFIG", fallback="")
|
||||
|
||||
split_config = cfg.split(",")
|
||||
for name in list(self.checkboxes.keys())[:4]:
|
||||
if name in split_config:
|
||||
split_config.remove(name)
|
||||
cfg = ",".join(split_config)
|
||||
|
||||
for var_name, checkbox in list(self.checkboxes.items())[:4]: # first three are by default activated
|
||||
if not checkbox.isChecked():
|
||||
if cfg:
|
||||
cfg += f",{var_name}=0"
|
||||
else:
|
||||
cfg = f"{var_name}=0"
|
||||
if cfg:
|
||||
config_helper.add_option(f"{self.name}.env", "MANGOHUD_CONFIG", cfg)
|
||||
self.environ_changed.emit(self.config_env_var_name)
|
||||
else:
|
||||
config_helper.remove_option(f"{self.name}.env", "MANGOHUD_CONFIG")
|
||||
self.environ_changed.emit(self.config_env_var_name)
|
||||
|
||||
self.set_wrapper_activated.emit(True)
|
337
rare/components/tabs/settings/widgets/overlay.py
Normal file
337
rare/components/tabs/settings/widgets/overlay.py
Normal file
|
@ -0,0 +1,337 @@
|
|||
from abc import abstractmethod
|
||||
from enum import IntEnum
|
||||
from logging import getLogger
|
||||
from typing import List, Dict, Tuple, Union, Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, Qt
|
||||
from PyQt5.QtGui import QIntValidator, QDoubleValidator, QShowEvent
|
||||
from PyQt5.QtWidgets import QGroupBox, QCheckBox, QLineEdit, QComboBox
|
||||
|
||||
from rare.ui.components.tabs.settings.widgets.overlay import Ui_OverlaySettings
|
||||
from rare.utils import config_helper as config
|
||||
|
||||
logger = getLogger("GameOverlays")
|
||||
|
||||
|
||||
class OverlayLineEdit(QLineEdit):
|
||||
def __init__(self, option: str, placeholder: str, parent=None):
|
||||
self.option = option
|
||||
super(OverlayLineEdit, self).__init__(parent=parent)
|
||||
self.valueChanged = self.textChanged
|
||||
|
||||
self.setPlaceholderText(placeholder)
|
||||
|
||||
def setDefault(self):
|
||||
self.setText("")
|
||||
|
||||
def getValue(self) -> Optional[str]:
|
||||
return f"{self.option}={text}" if (text := self.text()) else None
|
||||
|
||||
def setValue(self, options: Dict[str, str]):
|
||||
if (value := options.get(self.option, None)) is not None:
|
||||
self.setText(value)
|
||||
options.pop(self.option)
|
||||
else:
|
||||
self.setDefault()
|
||||
|
||||
|
||||
class OverlayComboBox(QComboBox):
|
||||
def __init__(self, option: str, parent=None):
|
||||
self.option = option
|
||||
super(OverlayComboBox, self).__init__(parent=parent)
|
||||
self.valueChanged = self.currentIndexChanged
|
||||
|
||||
def setDefault(self):
|
||||
self.setCurrentIndex(0)
|
||||
|
||||
def getValue(self) -> Optional[str]:
|
||||
return f"{self.option}={self.currentText()}" if self.currentIndex() > 0 else None
|
||||
|
||||
def setValue(self, options: Dict[str, str]):
|
||||
if (value := options.get(self.option, None)) is not None:
|
||||
self.setCurrentText(value)
|
||||
options.pop(self.option)
|
||||
else:
|
||||
self.setDefault()
|
||||
|
||||
|
||||
class OverlayCheckBox(QCheckBox):
|
||||
def __init__(self, option: str, title: str, desc: str = "", default_enabled: bool = False, parent=None):
|
||||
self.option = option
|
||||
super().__init__(title, parent=parent)
|
||||
self.setChecked(default_enabled)
|
||||
self.default_enabled = default_enabled
|
||||
self.setToolTip(desc)
|
||||
|
||||
def setDefault(self):
|
||||
self.setChecked(self.default_enabled)
|
||||
|
||||
def getValue(self) -> Optional[str]:
|
||||
# lk: return the check state in case of non-default, otherwise None
|
||||
checked = self.isChecked()
|
||||
value = f"{self.option}={int(checked)}" if self.default_enabled else self.option
|
||||
return value if checked ^ self.default_enabled else None
|
||||
|
||||
def setValue(self, options: Dict[str, str]):
|
||||
if options.get(self.option, None) is not None:
|
||||
self.setChecked(not self.default_enabled)
|
||||
options.pop(self.option)
|
||||
else:
|
||||
self.setChecked(self.default_enabled)
|
||||
|
||||
|
||||
class OverlayStringInput(OverlayLineEdit):
|
||||
def __init__(self, option: str, placeholder: str, parent=None):
|
||||
super().__init__(option, placeholder, parent=parent)
|
||||
|
||||
|
||||
class OverlayNumberInput(OverlayLineEdit):
|
||||
def __init__(self, option: str, placeholder: Union[int, float], parent=None):
|
||||
super().__init__(option, str(placeholder), parent=parent)
|
||||
validator = QDoubleValidator(self) if isinstance(placeholder, float) else QIntValidator(self)
|
||||
self.setValidator(validator)
|
||||
|
||||
|
||||
class OverlaySelectInput(OverlayComboBox):
|
||||
def __init__(self, option: str, values: List, parent=None):
|
||||
super().__init__(option, parent=parent)
|
||||
# self.addItems([str(v) for v in values])
|
||||
self.addItems(map(str, values))
|
||||
|
||||
|
||||
class ActivationStates(IntEnum):
|
||||
GLOBAL = -1
|
||||
DISABLED = 0
|
||||
DEFAULTS = 1
|
||||
CUSTOM = 2
|
||||
|
||||
|
||||
class OverlaySettings(QGroupBox):
|
||||
# str: option key
|
||||
environ_changed = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(OverlaySettings, self).__init__(parent=parent)
|
||||
self.ui = Ui_OverlaySettings()
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.ui.show_overlay_combo.addItem(self.tr("Global"), ActivationStates.GLOBAL)
|
||||
self.ui.show_overlay_combo.addItem(self.tr("Disabled"), ActivationStates.DISABLED)
|
||||
self.ui.show_overlay_combo.addItem(self.tr("Enabled (defaults)"), ActivationStates.DEFAULTS)
|
||||
self.ui.show_overlay_combo.addItem(self.tr("Enabled (custom)"), ActivationStates.CUSTOM)
|
||||
|
||||
self.envvar: str = None
|
||||
self.force_disabled: str = None
|
||||
self.force_defaults: str = None
|
||||
self.app_name: str = "default"
|
||||
|
||||
self.option_widgets: List[Union[OverlayCheckBox, OverlayLineEdit, OverlayComboBox]] = []
|
||||
# self.checkboxes: Dict[str, OverlayCheckBox] = {}
|
||||
# self.values: Dict[str, Union[OverlayLineEdit, OverlayComboBox]] = {}
|
||||
|
||||
self.ui.options_group.setTitle(self.tr("Custom options"))
|
||||
self.ui.show_overlay_combo.currentIndexChanged.connect(self.update_settings)
|
||||
|
||||
def setupWidget(
|
||||
self,
|
||||
grid_map: List[OverlayCheckBox],
|
||||
form_map: List[Tuple[Union[OverlayLineEdit, OverlayComboBox], str]],
|
||||
envvar: str,
|
||||
force_disabled: str,
|
||||
force_defaults: str,
|
||||
):
|
||||
self.envvar = envvar
|
||||
self.force_disabled = force_disabled
|
||||
self.force_defaults = force_defaults
|
||||
|
||||
for i, widget in enumerate(grid_map):
|
||||
widget.setParent(self.ui.options_group)
|
||||
self.ui.options_grid.addWidget(widget, i // 4, i % 4)
|
||||
# self.checkboxes[widget.option] = widget
|
||||
self.option_widgets.append(widget)
|
||||
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)
|
||||
|
||||
@abstractmethod
|
||||
def update_settings_override(self, state: ActivationStates):
|
||||
raise NotImplementedError
|
||||
|
||||
def update_settings(self):
|
||||
current_state = self.ui.show_overlay_combo.currentData(Qt.UserRole)
|
||||
self.ui.options_group.setEnabled(current_state == ActivationStates.CUSTOM)
|
||||
|
||||
if current_state == ActivationStates.GLOBAL:
|
||||
# System default (don't add any env variables)
|
||||
config.remove_envvar(self.app_name, self.envvar)
|
||||
|
||||
elif current_state == ActivationStates.DISABLED:
|
||||
# hidden
|
||||
config.set_envvar(self.app_name, self.envvar, self.force_disabled)
|
||||
|
||||
elif current_state == ActivationStates.DEFAULTS:
|
||||
config.set_envvar(self.app_name, self.envvar, self.force_defaults)
|
||||
|
||||
elif current_state == ActivationStates.CUSTOM:
|
||||
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.envvar, ",".join(options))
|
||||
|
||||
self.environ_changed.emit(self.envvar)
|
||||
self.update_settings_override(current_state)
|
||||
|
||||
def setCurrentState(self, state: ActivationStates):
|
||||
self.ui.show_overlay_combo.setCurrentIndex(self.ui.show_overlay_combo.findData(state, Qt.UserRole))
|
||||
self.ui.options_group.setEnabled(state == ActivationStates.CUSTOM)
|
||||
|
||||
def showEvent(self, a0: QShowEvent):
|
||||
if a0.spontaneous():
|
||||
return super().showEvent(a0)
|
||||
self.ui.options_group.blockSignals(True)
|
||||
|
||||
config_options = config.get_envvar(self.app_name, self.envvar, fallback=None)
|
||||
if config_options is None:
|
||||
logger.debug("Overlay setting %s is not present", self.envvar)
|
||||
self.setCurrentState(ActivationStates.GLOBAL)
|
||||
|
||||
elif config_options == self.force_disabled:
|
||||
self.setCurrentState(ActivationStates.DISABLED)
|
||||
|
||||
elif config_options == self.force_defaults:
|
||||
self.setCurrentState(ActivationStates.DEFAULTS)
|
||||
|
||||
else:
|
||||
self.setCurrentState(ActivationStates.CUSTOM)
|
||||
opts = {}
|
||||
for o in config_options.split(","):
|
||||
if "=" in o:
|
||||
k, v = o.split("=")
|
||||
opts[k] = v
|
||||
else:
|
||||
# lk: The value doesn't matter other than not being None
|
||||
opts[o] = "enable"
|
||||
|
||||
for widget in self.option_widgets:
|
||||
widget.setValue(opts)
|
||||
if opts:
|
||||
logger.info("Remaining options without a gui switch: %s", ",".join(opts.keys()))
|
||||
|
||||
self.ui.options_group.blockSignals(False)
|
||||
return super().showEvent(a0)
|
||||
|
||||
|
||||
class DxvkSettings(OverlaySettings):
|
||||
def __init__(self, parent=None):
|
||||
super(DxvkSettings, self).__init__(parent=parent)
|
||||
self.setTitle(self.tr("DXVK settings"))
|
||||
grid = [
|
||||
OverlayCheckBox("fps", self.tr("FPS")),
|
||||
OverlayCheckBox("frametime", self.tr("Frametime")),
|
||||
OverlayCheckBox("memory", self.tr("Memory usage")),
|
||||
OverlayCheckBox("gpuload", self.tr("GPU usage")),
|
||||
OverlayCheckBox("devinfo", self.tr("Device info")),
|
||||
OverlayCheckBox("version", self.tr("DXVK version")),
|
||||
OverlayCheckBox("api", self.tr("D3D feature level")),
|
||||
OverlayCheckBox("compiler", self.tr("Compiler activity")),
|
||||
]
|
||||
form = [(OverlayNumberInput("scale", 1.0), self.tr("Scale"))]
|
||||
self.setupWidget(grid, form, "DXVK_HUD", "0", "1")
|
||||
|
||||
def update_settings_override(self, state: ActivationStates):
|
||||
pass
|
||||
|
||||
|
||||
mangohud_position = [
|
||||
"default",
|
||||
"top-left",
|
||||
"top-right",
|
||||
"middle-left",
|
||||
"middle-right",
|
||||
"bottom-left",
|
||||
"bottom-right",
|
||||
"top-center",
|
||||
]
|
||||
|
||||
|
||||
class MangoHudSettings(OverlaySettings):
|
||||
def __init__(self, parent=None):
|
||||
super(MangoHudSettings, self).__init__(parent=parent)
|
||||
self.setTitle(self.tr("MangoHud settings"))
|
||||
grid = [
|
||||
OverlayCheckBox("read_cfg", self.tr("Read config")),
|
||||
OverlayCheckBox("fps", self.tr("FPS"), default_enabled=True),
|
||||
OverlayCheckBox("frame_timing", self.tr("Frame time"), default_enabled=True),
|
||||
OverlayCheckBox("cpu_stats", self.tr("CPU load"), default_enabled=True),
|
||||
OverlayCheckBox("gpu_stats", self.tr("GPU load"), default_enabled=True),
|
||||
OverlayCheckBox("cpu_temp", self.tr("CPU temperature")),
|
||||
OverlayCheckBox("gpu_temp", self.tr("GPU temperature")),
|
||||
OverlayCheckBox("ram", self.tr("Memory usage")),
|
||||
OverlayCheckBox("vram", self.tr("VRAM usage")),
|
||||
OverlayCheckBox("time", self.tr("Local time")),
|
||||
OverlayCheckBox("version", self.tr("MangoHud version")),
|
||||
OverlayCheckBox("arch", self.tr("System architecture")),
|
||||
OverlayCheckBox("histogram", self.tr("FPS graph")),
|
||||
OverlayCheckBox("gpu_name", self.tr("GPU name")),
|
||||
OverlayCheckBox("cpu_power", self.tr("CPU power consumption")),
|
||||
OverlayCheckBox("gpu_power", self.tr("GPU power consumption")),
|
||||
]
|
||||
form = [
|
||||
(OverlayNumberInput("font_size", 24), self.tr("Font size")),
|
||||
(OverlaySelectInput("position", mangohud_position), self.tr("Position")),
|
||||
]
|
||||
|
||||
self.setupWidget(grid, form, "MANGOHUD_CONFIG", "no_display", "read_cfg")
|
||||
|
||||
def showEvent(self, a0: QShowEvent):
|
||||
if a0.spontaneous():
|
||||
return super().showEvent(a0)
|
||||
self.ui.options_group.blockSignals(True)
|
||||
self.ui.options_group.blockSignals(False)
|
||||
return super().showEvent(a0)
|
||||
|
||||
def update_settings_override(self, state: IntEnum): # pylint: disable=E0202
|
||||
if state == ActivationStates.GLOBAL:
|
||||
config.remove_envvar(self.app_name, "MANGOHUD")
|
||||
|
||||
elif state == ActivationStates.DISABLED:
|
||||
config.set_envvar(self.app_name, "MANGOHUD", "0")
|
||||
|
||||
elif state == ActivationStates.DEFAULTS:
|
||||
config.set_envvar(self.app_name, "MANGOHUD", "1")
|
||||
|
||||
elif state == ActivationStates.CUSTOM:
|
||||
config.set_envvar(self.app_name, "MANGOHUD", "1")
|
||||
|
||||
self.environ_changed.emit("MANGOHUD")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
from PyQt5.QtWidgets import QApplication, QDialog, QVBoxLayout
|
||||
|
||||
from legendary.core import LegendaryCore
|
||||
|
||||
core = LegendaryCore()
|
||||
config.init_config_handler(core)
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
dlg = QDialog()
|
||||
|
||||
dxvk = DxvkSettings(dlg)
|
||||
mangohud = MangoHudSettings(dlg)
|
||||
|
||||
layout = QVBoxLayout(dlg)
|
||||
layout.addWidget(dxvk)
|
||||
layout.addWidget(mangohud)
|
||||
|
||||
dlg.show()
|
||||
ret = app.exec()
|
||||
config.save_config()
|
||||
sys.exit(ret)
|
|
@ -1,190 +0,0 @@
|
|||
from enum import Enum
|
||||
from logging import getLogger
|
||||
from typing import List, Dict, Tuple, Any, Callable
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5.QtGui import QIntValidator, QDoubleValidator
|
||||
from PyQt5.QtWidgets import QGroupBox, QCheckBox, QWidget, QLineEdit, QLabel, QComboBox
|
||||
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.ui.components.tabs.settings.widgets.overlay import Ui_OverlaySettings
|
||||
from rare.utils import config_helper
|
||||
|
||||
logger = getLogger("GameOverlays")
|
||||
|
||||
|
||||
class TextInputField(QLineEdit):
|
||||
def __init__(self):
|
||||
super(TextInputField, self).__init__()
|
||||
self.value_changed = self.textChanged
|
||||
self.set_value = self.setText
|
||||
self.set_default = lambda: self.setText("")
|
||||
|
||||
def get_value(self):
|
||||
return self.text()
|
||||
|
||||
|
||||
class ComboBox(QComboBox):
|
||||
def __init__(self):
|
||||
super(ComboBox, self).__init__()
|
||||
self.value_changed = self.currentIndexChanged
|
||||
self.get_value = self.currentText
|
||||
self.set_value = self.setCurrentText
|
||||
self.set_default = lambda: self.setCurrentIndex(0)
|
||||
|
||||
|
||||
class CustomOption:
|
||||
input_field: QWidget
|
||||
var_name: str
|
||||
|
||||
@classmethod
|
||||
def string_input(cls, var_name: str, placeholder: str):
|
||||
tmp = cls()
|
||||
tmp.input_field = TextInputField()
|
||||
tmp.var_name = var_name
|
||||
tmp.input_field.setPlaceholderText(placeholder)
|
||||
return tmp
|
||||
|
||||
@classmethod
|
||||
def number_input(cls, var_name: str, placeholder: Any, is_float: bool = False):
|
||||
tmp = cls()
|
||||
tmp.input_field = TextInputField()
|
||||
tmp.var_name = var_name
|
||||
tmp.input_field.setPlaceholderText(str(placeholder))
|
||||
if is_float:
|
||||
validator = QDoubleValidator()
|
||||
else:
|
||||
validator = QIntValidator()
|
||||
tmp.input_field.setValidator(validator)
|
||||
return tmp
|
||||
|
||||
@classmethod
|
||||
def select_input(cls, var_name: str, options: List[str]):
|
||||
"""options: default value in options[0]"""
|
||||
tmp = cls()
|
||||
tmp.input_field = ComboBox()
|
||||
tmp.var_name = var_name
|
||||
tmp.input_field.addItems(options)
|
||||
return tmp
|
||||
|
||||
|
||||
class ActivationStates(Enum):
|
||||
DEFAULT = 0
|
||||
HIDDEN = 1
|
||||
ACTIVATED = 2
|
||||
|
||||
|
||||
class OverlaySettings(QGroupBox, Ui_OverlaySettings):
|
||||
# str: option key
|
||||
environ_changed = pyqtSignal(str)
|
||||
name: str = "default"
|
||||
settings_updatable = True
|
||||
|
||||
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__()
|
||||
self.setupUi(self)
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.config_env_var_name = config_env_var_name
|
||||
self.no_display_value = no_display_value
|
||||
self.set_activation_state = set_activation_state
|
||||
|
||||
self.checkboxes: Dict[str, QCheckBox] = {}
|
||||
|
||||
for i, (var_name, translated_text) in enumerate(checkboxes_map):
|
||||
cb = QCheckBox(translated_text)
|
||||
self.options_grid.addWidget(cb, i // 4, i % 4)
|
||||
self.checkboxes[var_name] = cb
|
||||
cb.stateChanged.connect(self.update_settings)
|
||||
|
||||
self.values: Dict[str, QWidget] = {}
|
||||
|
||||
num_rows = len(checkboxes_map) // 4
|
||||
for custom_option, translated_text in value_map:
|
||||
input_field = custom_option.input_field
|
||||
self.options_form.addRow(QLabel(translated_text), input_field)
|
||||
self.values[custom_option.var_name] = input_field
|
||||
input_field.value_changed.connect(self.update_settings)
|
||||
num_rows += 1
|
||||
|
||||
self.show_overlay_combo.currentIndexChanged.connect(self.update_settings)
|
||||
|
||||
def update_settings(self):
|
||||
if not self.settings_updatable:
|
||||
return
|
||||
if self.show_overlay_combo.currentIndex() == 0:
|
||||
# System default
|
||||
config_helper.remove_option(f"{self.name}.env", self.config_env_var_name)
|
||||
self.environ_changed.emit(self.config_env_var_name)
|
||||
self.gb_options.setDisabled(True)
|
||||
self.set_activation_state(ActivationStates.DEFAULT)
|
||||
return
|
||||
|
||||
elif self.show_overlay_combo.currentIndex() == 1:
|
||||
# hidden
|
||||
config_helper.add_option(f"{self.name}.env", self.config_env_var_name, self.no_display_value)
|
||||
self.environ_changed.emit(self.config_env_var_name)
|
||||
self.gb_options.setDisabled(True)
|
||||
self.set_activation_state(ActivationStates.HIDDEN)
|
||||
return
|
||||
elif self.show_overlay_combo.currentIndex() == 2:
|
||||
self.gb_options.setDisabled(False)
|
||||
# custom options
|
||||
var_names = []
|
||||
for var_name, cb in self.checkboxes.items():
|
||||
if cb.isChecked():
|
||||
var_names.append(var_name)
|
||||
|
||||
for var_name, input_field in self.values.items():
|
||||
text = input_field.get_value()
|
||||
if text not in ["default", ""]:
|
||||
var_names.append(f"{var_name}={text}")
|
||||
|
||||
if not var_names:
|
||||
list(self.checkboxes.values())[0].setChecked(True)
|
||||
var_names.append(list(self.checkboxes.keys())[0])
|
||||
|
||||
config_helper.add_option(f"{self.name}.env", self.config_env_var_name, ",".join(var_names))
|
||||
self.environ_changed.emit(self.config_env_var_name)
|
||||
self.set_activation_state(ActivationStates.ACTIVATED)
|
||||
|
||||
def load_settings(self, name: str):
|
||||
self.settings_updatable = False
|
||||
# load game specific
|
||||
self.name = name
|
||||
|
||||
for checkbox in self.checkboxes.values():
|
||||
checkbox.setChecked(False)
|
||||
for input_field in self.values.values():
|
||||
input_field.set_default()
|
||||
|
||||
options = self.core.lgd.config.get(f"{self.name}.env", self.config_env_var_name, fallback=None)
|
||||
if options is None:
|
||||
logger.debug(f"No Overlay settings found {self.config_env_var_name}")
|
||||
self.show_overlay_combo.setCurrentIndex(0)
|
||||
self.gb_options.setDisabled(True)
|
||||
|
||||
elif options == self.no_display_value:
|
||||
# not visible
|
||||
self.gb_options.setDisabled(True)
|
||||
self.show_overlay_combo.setCurrentIndex(1)
|
||||
|
||||
else:
|
||||
self.show_overlay_combo.setCurrentIndex(2)
|
||||
for option in options.split(","):
|
||||
try:
|
||||
if "=" in option:
|
||||
var_name, value = option.split("=")
|
||||
if var_name in self.checkboxes.keys():
|
||||
self.checkboxes[var_name].setChecked(False)
|
||||
else:
|
||||
self.values[var_name].set_value(value)
|
||||
else:
|
||||
self.checkboxes[option].setChecked(True)
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
|
||||
self.gb_options.setDisabled(False)
|
||||
|
||||
self.settings_updatable = True
|
|
@ -1,61 +0,0 @@
|
|||
import os
|
||||
import shutil
|
||||
from typing import Tuple
|
||||
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QCheckBox, QFileDialog
|
||||
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.utils import config_helper
|
||||
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
|
||||
|
||||
|
||||
class PreLaunchSettings(QHBoxLayout):
|
||||
app_name: str
|
||||
|
||||
def __init__(self):
|
||||
super(PreLaunchSettings, self).__init__()
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.edit = PathEdit(
|
||||
path="",
|
||||
placeholder=self.tr("Path to script"),
|
||||
file_mode=QFileDialog.ExistingFile,
|
||||
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)
|
||||
|
||||
def edit_command(self, text: str) -> Tuple[bool, str, int]:
|
||||
if not text.strip():
|
||||
return True, text, IndicatorReasonsCommon.VALID
|
||||
|
||||
if not os.path.isfile(text.split()[0]) and not shutil.which(text.split()[0]):
|
||||
return False, text, IndicatorReasonsCommon.FILE_NOT_EXISTS
|
||||
else:
|
||||
return True, text, IndicatorReasonsCommon.VALID
|
||||
|
||||
def save_pre_launch_command(self, text):
|
||||
if text:
|
||||
config_helper.add_option(self.app_name, "pre_launch_command", text)
|
||||
self.wait_check.setDisabled(False)
|
||||
else:
|
||||
config_helper.remove_option(self.app_name, "pre_launch_command")
|
||||
self.wait_check.setDisabled(True)
|
||||
config_helper.remove_option(self.app_name, "pre_launch_wait")
|
||||
|
||||
def save_wait_finish(self):
|
||||
config_helper.add_option(self.app_name, "pre_launch_wait", str(self.wait_check.isChecked()).lower())
|
||||
|
||||
def load_settings(self, app_name):
|
||||
self.app_name = app_name
|
||||
|
||||
command = self.core.lgd.config.get(app_name, "pre_launch_command", fallback="")
|
||||
self.edit.setText(command)
|
||||
|
||||
wait = self.core.lgd.config.getboolean(app_name, "pre_launch_wait", fallback=False)
|
||||
self.wait_check.setChecked(wait)
|
||||
|
||||
self.wait_check.setEnabled(bool(command))
|
|
@ -1,85 +1,122 @@
|
|||
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.QtWidgets import QGroupBox, QFileDialog
|
||||
from PyQt5.QtCore import pyqtSignal, Qt
|
||||
from PyQt5.QtGui import QShowEvent
|
||||
from PyQt5.QtWidgets import QGroupBox, QFileDialog, QFormLayout, QComboBox
|
||||
|
||||
from rare.components.tabs.settings import LinuxSettings
|
||||
from rare.shared import LegendaryCoreSingleton
|
||||
from rare.ui.components.tabs.settings.proton import Ui_ProtonSettings
|
||||
from rare.utils import config_helper, proton
|
||||
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 config
|
||||
from rare.utils.compat import steam
|
||||
from rare.utils.paths import proton_compat_dir
|
||||
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__()
|
||||
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()
|
||||
def __init__(self, parent=None):
|
||||
super(ProtonSettings, self).__init__(parent=parent)
|
||||
self.setTitle(self.tr("Proton settings"))
|
||||
|
||||
self.ui.proton_combo.addItems(self.possible_proton_combos)
|
||||
self.ui.proton_combo.currentIndexChanged.connect(self.change_proton)
|
||||
self.tool_combo = QComboBox(self)
|
||||
self.tool_combo.currentIndexChanged.connect(self.__on_proton_changed)
|
||||
|
||||
self.proton_prefix = PathEdit(
|
||||
self.tool_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"),
|
||||
parent=self
|
||||
)
|
||||
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")
|
||||
layout = QFormLayout(self)
|
||||
layout.addRow(self.tr("Proton tool"), self.tool_combo)
|
||||
layout.addRow(self.tr("Compat data"), self.tool_prefix)
|
||||
layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
|
||||
layout.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
layout.setFormAlignment(Qt.AlignLeading | Qt.AlignTop)
|
||||
|
||||
self.proton_prefix.setEnabled(False)
|
||||
self.proton_prefix.setText("")
|
||||
self.app_name: str = "default"
|
||||
self.core = RareCore.instance().core()
|
||||
self.wrappers: Wrappers = RareCore.instance().wrappers()
|
||||
self.tool_wrapper: Optional[Wrapper] = None
|
||||
|
||||
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.tool_combo.blockSignals(True)
|
||||
self.tool_combo.clear()
|
||||
self.tool_combo.addItem(self.tr("Don't use a compatibility tool"), None)
|
||||
tools = steam.find_tools()
|
||||
for tool in tools:
|
||||
self.tool_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.tool_combo.findData(tool)
|
||||
except StopIteration:
|
||||
index = 0
|
||||
self.tool_combo.setCurrentIndex(index)
|
||||
self.tool_combo.blockSignals(False)
|
||||
|
||||
self.proton_prefix.setText(os.path.expanduser("~/.proton"))
|
||||
enabled = bool(self.tool_combo.currentData(Qt.UserRole))
|
||||
self.tool_prefix.blockSignals(True)
|
||||
self.tool_prefix.setText(config.get_proton_compatdata(self.app_name, fallback=""))
|
||||
self.tool_prefix.setEnabled(enabled)
|
||||
self.tool_prefix.blockSignals(False)
|
||||
|
||||
# Don't use Wine
|
||||
self._linux_settings.wine_exec.setText("")
|
||||
self._linux_settings.wine_prefix.setText("")
|
||||
super().showEvent(a0)
|
||||
|
||||
config_helper.save_config()
|
||||
def __on_proton_changed(self, index):
|
||||
steam_tool: Union[steam.ProtonTool, steam.CompatibilityTool] = self.tool_combo.itemData(index)
|
||||
|
||||
def proton_prefix_edit(self, text: str) -> Tuple[bool, str, int]:
|
||||
steam_environ = steam.get_steam_environment(steam_tool, self.tool_prefix.text())
|
||||
for key, value in steam_environ.items():
|
||||
config.save_envvar(self.app_name, key, value)
|
||||
self.environ_changed.emit(key)
|
||||
|
||||
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)
|
||||
|
||||
self.tool_prefix.setEnabled(steam_tool is not None)
|
||||
if steam_tool:
|
||||
if not (compatdata_path := config.get_proton_compatdata(self.app_name, fallback="")):
|
||||
compatdata_path = proton_compat_dir(self.app_name)
|
||||
config.save_proton_compatdata(self.app_name, str(compatdata_path))
|
||||
target = compatdata_path.joinpath("pfx")
|
||||
if not target.is_dir():
|
||||
os.makedirs(target, exist_ok=True)
|
||||
self.tool_prefix.setText(str(compatdata_path))
|
||||
else:
|
||||
self.tool_prefix.setText("")
|
||||
|
||||
self.tool_enabled.emit(steam_tool is not None)
|
||||
|
||||
@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 +125,6 @@ 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.save_proton_compatdata(self.app_name, text)
|
||||
self.environ_changed.emit("STEAM_COMPAT_DATA_PATH")
|
||||
config_helper.save_config()
|
||||
|
||||
def load_settings(self, app_name: str, proton: str):
|
||||
self.changeable = False
|
||||
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,33 +2,35 @@ 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
|
||||
|
||||
|
||||
class RPCSettings(QGroupBox, Ui_RPCSettings):
|
||||
class RPCSettings(QGroupBox):
|
||||
def __init__(self, parent):
|
||||
super(RPCSettings, self).__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
self.ui = Ui_RPCSettings()
|
||||
self.ui.setupUi(self)
|
||||
self.signals = GlobalSignalsSingleton()
|
||||
|
||||
self.settings = QSettings()
|
||||
|
||||
self.enable.setCurrentIndex(self.settings.value("rpc_enable", 0, int))
|
||||
self.enable.currentIndexChanged.connect(self.changed)
|
||||
self.ui.enable.setCurrentIndex(self.settings.value(*options.rpc_enable))
|
||||
self.ui.enable.currentIndexChanged.connect(self.__enable_changed)
|
||||
|
||||
self.show_game.setChecked((self.settings.value("rpc_name", True, bool)))
|
||||
self.show_game.stateChanged.connect(
|
||||
lambda: self.settings.setValue("rpc_game", self.show_game.isChecked())
|
||||
self.ui.show_game.setChecked((self.settings.value(*options.rpc_name)))
|
||||
self.ui.show_game.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.rpc_name.key, self.ui.show_game.isChecked())
|
||||
)
|
||||
|
||||
self.show_os.setChecked((self.settings.value("rpc_os", True, bool)))
|
||||
self.show_os.stateChanged.connect(
|
||||
lambda: self.settings.setValue("rpc_os", self.show_os.isChecked())
|
||||
self.ui.show_os.setChecked((self.settings.value(*options.rpc_os)))
|
||||
self.ui.show_os.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.rpc_os.key, self.ui.show_os.isChecked())
|
||||
)
|
||||
|
||||
self.show_time.setChecked((self.settings.value("rpc_time", True, bool)))
|
||||
self.show_time.stateChanged.connect(
|
||||
lambda: self.settings.setValue("rpc_time", self.show_time.isChecked())
|
||||
self.ui.show_time.setChecked((self.settings.value(*options.rpc_time)))
|
||||
self.ui.show_time.stateChanged.connect(
|
||||
lambda: self.settings.setValue(options.rpc_time.key, self.ui.show_time.isChecked())
|
||||
)
|
||||
|
||||
try:
|
||||
|
@ -37,6 +39,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()
|
||||
|
|
91
rare/components/tabs/settings/widgets/wine.py
Normal file
91
rare/components/tabs/settings/widgets/wine.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
import os
|
||||
from logging import getLogger
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, Qt, QSignalBlocker
|
||||
from PyQt5.QtGui import QShowEvent
|
||||
from PyQt5.QtWidgets import QFileDialog, QFormLayout, QGroupBox
|
||||
|
||||
from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton
|
||||
from rare.utils import config_helper as config
|
||||
from rare.widgets.indicator_edit import PathEdit, IndicatorReasonsCommon
|
||||
|
||||
logger = getLogger("WineSettings")
|
||||
|
||||
|
||||
class WineSettings(QGroupBox):
|
||||
# str: option key
|
||||
environ_changed = pyqtSignal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(WineSettings, self).__init__(parent=parent)
|
||||
self.setTitle(self.tr("Wine settings"))
|
||||
|
||||
self.core = LegendaryCoreSingleton()
|
||||
self.signals = GlobalSignalsSingleton()
|
||||
|
||||
self.app_name: Optional[str] = "default"
|
||||
|
||||
# Wine prefix
|
||||
self.wine_prefix = PathEdit(
|
||||
path="",
|
||||
file_mode=QFileDialog.DirectoryOnly,
|
||||
edit_func=lambda path: (os.path.isdir(path) or not path, path, IndicatorReasonsCommon.DIR_NOT_EXISTS),
|
||||
save_func=self.save_prefix,
|
||||
)
|
||||
|
||||
# Wine executable
|
||||
self.wine_exec = PathEdit(
|
||||
path="",
|
||||
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=self.save_exec,
|
||||
)
|
||||
|
||||
layout = QFormLayout(self)
|
||||
layout.addRow(self.tr("Executable"), self.wine_exec)
|
||||
layout.addRow(self.tr("Prefix"), self.wine_prefix)
|
||||
layout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow)
|
||||
layout.setLabelAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
layout.setFormAlignment(Qt.AlignLeading | Qt.AlignTop)
|
||||
|
||||
def showEvent(self, a0: QShowEvent):
|
||||
if a0.spontaneous():
|
||||
return super().showEvent(a0)
|
||||
|
||||
_ = QSignalBlocker(self.wine_prefix)
|
||||
self.wine_prefix.setText(self.load_prefix())
|
||||
_ = QSignalBlocker(self.wine_exec)
|
||||
self.wine_exec.setText(self.load_exec())
|
||||
self.setDisabled(config.get_boolean(self.app_name, "no_wine", fallback=False))
|
||||
|
||||
return super().showEvent(a0)
|
||||
|
||||
def tool_enabled(self, enabled: bool):
|
||||
if enabled:
|
||||
config.set_boolean(self.app_name, "no_wine", True)
|
||||
else:
|
||||
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 config.get_wine_prefix(self.app_name, "")
|
||||
|
||||
def save_prefix(self, path: str) -> None:
|
||||
if self.app_name is None:
|
||||
raise RuntimeError
|
||||
config.save_wine_prefix(self.app_name, path)
|
||||
self.environ_changed.emit("WINEPREFIX")
|
||||
|
||||
def load_exec(self) -> str:
|
||||
if self.app_name is None:
|
||||
raise RuntimeError
|
||||
return config.get_option(self.app_name, "wine_executable", "")
|
||||
|
||||
def save_exec(self, text: str) -> None:
|
||||
if self.app_name is None:
|
||||
raise RuntimeError
|
||||
config.save_option(self.app_name, "wine_executable", text)
|
|
@ -1,356 +0,0 @@
|
|||
import re
|
||||
import shutil
|
||||
from logging import getLogger
|
||||
from typing import Dict, Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QSettings, QSize, Qt, QMimeData, pyqtSlot, QCoreApplication
|
||||
from PyQt5.QtGui import QDrag, QDropEvent, QDragEnterEvent, QDragMoveEvent, QFont, QMouseEvent
|
||||
from PyQt5.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QInputDialog,
|
||||
QFrame,
|
||||
QMessageBox,
|
||||
QSizePolicy,
|
||||
QWidget,
|
||||
QScrollArea,
|
||||
QAction,
|
||||
QToolButton,
|
||||
QMenu,
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
logger = getLogger("WrapperSettings")
|
||||
|
||||
extra_wrapper_regex = {
|
||||
"proton": "\".*proton\" run", # proton
|
||||
"mangohud": "mangohud" # mangohud
|
||||
}
|
||||
|
||||
|
||||
class Wrapper:
|
||||
pass
|
||||
|
||||
|
||||
class WrapperWidget(QFrame):
|
||||
update_wrapper = pyqtSignal(str, str)
|
||||
delete_wrapper = pyqtSignal(str)
|
||||
|
||||
def __init__(self, text: str, show_text=None, 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.text = text
|
||||
self.setToolTip(text)
|
||||
|
||||
unmanaged = show_text in extra_wrapper_regex.keys()
|
||||
|
||||
text_lbl = QLabel(show_text, parent=self)
|
||||
text_lbl.setFont(QFont("monospace"))
|
||||
text_lbl.setDisabled(unmanaged)
|
||||
|
||||
image_lbl = QLabel(parent=self)
|
||||
image_lbl.setPixmap(icon("mdi.drag-vertical").pixmap(QSize(20, 20)))
|
||||
|
||||
edit_action = QAction("Edit", parent=self)
|
||||
edit_action.triggered.connect(self.__edit)
|
||||
delete_action = QAction("Delete", parent=self)
|
||||
delete_action.triggered.connect(self.__delete)
|
||||
|
||||
manage_menu = QMenu(parent=self)
|
||||
manage_menu.addActions([edit_action, delete_action])
|
||||
|
||||
manage_button = QToolButton(parent=self)
|
||||
manage_button.setIcon(icon("mdi.menu"))
|
||||
manage_button.setMenu(manage_menu)
|
||||
manage_button.setPopupMode(QToolButton.InstantPopup)
|
||||
manage_button.setDisabled(unmanaged)
|
||||
if unmanaged:
|
||||
manage_button.setToolTip(self.tr("Manage through settings"))
|
||||
else:
|
||||
manage_button.setToolTip(self.tr("Manage"))
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(image_lbl)
|
||||
layout.addWidget(text_lbl)
|
||||
layout.addWidget(manage_button)
|
||||
self.setLayout(layout)
|
||||
|
||||
# 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 __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)
|
||||
accepted = dialog.exec()
|
||||
wrapper = dialog.textValue()
|
||||
dialog.deleteLater()
|
||||
if accepted and wrapper:
|
||||
self.update_wrapper.emit(self.text, wrapper)
|
||||
|
||||
def mouseMoveEvent(self, a0: QMouseEvent) -> None:
|
||||
if a0.buttons() == Qt.LeftButton:
|
||||
a0.accept()
|
||||
drag = QDrag(self)
|
||||
mime = QMimeData()
|
||||
drag.setMimeData(mime)
|
||||
drag.exec_(Qt.MoveAction)
|
||||
|
||||
|
||||
class WrapperSettings(QWidget):
|
||||
def __init__(self):
|
||||
super(WrapperSettings, self).__init__()
|
||||
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.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.wrapper_scroll.horizontalScrollBar().rangeChanged.connect(self.adjust_scrollarea)
|
||||
|
||||
# lk: set object names for the stylesheet
|
||||
self.setObjectName(type(self).__name__)
|
||||
self.ui.no_wrapper_label.setObjectName(f"{self.objectName()}Label")
|
||||
self.wrapper_scroll.setObjectName(f"{self.objectName()}Scroll")
|
||||
self.wrapper_scroll.horizontalScrollBar().setObjectName(
|
||||
f"{self.wrapper_scroll.objectName()}Bar")
|
||||
self.wrapper_scroll.verticalScrollBar().setObjectName(
|
||||
f"{self.wrapper_scroll.objectName()}Bar")
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def adjust_scrollarea(self, min: int, max: int):
|
||||
wrapper_widget = self.scroll_content.findChild(WrapperWidget)
|
||||
if not wrapper_widget:
|
||||
return
|
||||
# lk: when the scrollbar is not visible, min and max are 0
|
||||
if max > min:
|
||||
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_scroll.horizontalScrollBar().sizeHint().height()
|
||||
)
|
||||
else:
|
||||
self.wrapper_scroll.setMaximumHeight(
|
||||
wrapper_widget.sizeHint().height()
|
||||
+ self.wrapper_scroll.rect().height()
|
||||
- self.wrapper_scroll.contentsRect().height()
|
||||
)
|
||||
|
||||
def get_wrapper_string(self):
|
||||
return " ".join(self.get_wrapper_list())
|
||||
|
||||
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):
|
||||
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()
|
||||
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
|
||||
|
||||
self.ui.widget_stack.setCurrentIndex(0)
|
||||
|
||||
if widget := self.wrappers.get(show_text, None):
|
||||
widget.deleteLater()
|
||||
|
||||
widget = WrapperWidget(text, show_text, self.scroll_content)
|
||||
if position < 0:
|
||||
self.scroll_content.layout().addWidget(widget)
|
||||
else:
|
||||
self.scroll_content.layout().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)
|
||||
|
||||
self.wrappers[show_text] = widget
|
||||
|
||||
if not from_load:
|
||||
self.save()
|
||||
|
||||
@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()
|
||||
|
||||
if not self.wrappers:
|
||||
self.wrapper_scroll.setMaximumHeight(self.ui.label_page.sizeHint().height())
|
||||
self.ui.widget_stack.setCurrentIndex(1)
|
||||
|
||||
self.save()
|
||||
|
||||
@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:
|
||||
self.wrapper_scroll.setMaximumHeight(self.ui.label_page.sizeHint().height())
|
||||
self.ui.widget_stack.setCurrentIndex(1)
|
||||
else:
|
||||
self.ui.widget_stack.setCurrentIndex(0)
|
||||
|
||||
self.save()
|
||||
|
||||
|
||||
class WrapperContainer(QWidget):
|
||||
|
||||
def __init__(self, save_cb, 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.drag_widget: Optional[QWidget] = None
|
||||
|
||||
# lk: set object names for the stylesheet
|
||||
self.setObjectName(type(self).__name__)
|
||||
|
||||
def dragEnterEvent(self, e: QDragEnterEvent):
|
||||
widget = e.source()
|
||||
self.drag_widget = widget
|
||||
e.accept()
|
||||
|
||||
def _get_drop_index(self, x):
|
||||
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:
|
||||
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:
|
||||
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)
|
||||
|
||||
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
|
||||
e.accept()
|
||||
self.save()
|
417
rare/components/tabs/settings/widgets/wrappers.py
Normal file
417
rare/components/tabs/settings/widgets/wrappers.py
Normal file
|
@ -0,0 +1,417 @@
|
|||
import platform as pf
|
||||
import shlex
|
||||
import shutil
|
||||
from logging import getLogger
|
||||
from typing import Optional, Tuple, Iterable
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QSize, Qt, QMimeData, pyqtSlot
|
||||
from PyQt5.QtGui import QDrag, QDropEvent, QDragEnterEvent, QDragMoveEvent, QFont, QMouseEvent, QShowEvent
|
||||
from PyQt5.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QFrame,
|
||||
QMessageBox,
|
||||
QSizePolicy,
|
||||
QWidget,
|
||||
QScrollArea,
|
||||
QAction,
|
||||
QToolButton,
|
||||
QMenu, QStackedWidget, QPushButton, QLineEdit, QVBoxLayout, QComboBox,
|
||||
)
|
||||
|
||||
from rare.models.wrapper import Wrapper
|
||||
from rare.shared import RareCore
|
||||
from rare.utils.misc import icon
|
||||
from rare.widgets.dialogs import ButtonDialog, game_title
|
||||
|
||||
if pf.system() in {"Linux", "FreeBSD"}:
|
||||
from rare.utils.compat import steam
|
||||
|
||||
logger = getLogger("WrapperSettings")
|
||||
|
||||
|
||||
class WrapperEditDialog(ButtonDialog):
|
||||
result_ready = pyqtSignal(bool, str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(WrapperEditDialog, self).__init__(parent=parent)
|
||||
|
||||
self.line_edit = QLineEdit(self)
|
||||
self.line_edit.textChanged.connect(self.__on_text_changed)
|
||||
|
||||
self.widget_layout = QVBoxLayout()
|
||||
self.widget_layout.addWidget(self.line_edit)
|
||||
|
||||
self.setCentralLayout(self.widget_layout)
|
||||
|
||||
self.accept_button.setText(self.tr("Save"))
|
||||
self.accept_button.setIcon(icon("fa.edit"))
|
||||
self.accept_button.setEnabled(False)
|
||||
|
||||
self.result: Tuple = ()
|
||||
|
||||
def setup(self, wrapper: Wrapper):
|
||||
header = self.tr("Edit wrapper")
|
||||
self.setWindowTitle(header)
|
||||
self.setSubtitle(game_title(header, wrapper.name))
|
||||
self.line_edit.setText(wrapper.as_str)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def __on_text_changed(self, text: str):
|
||||
self.accept_button.setEnabled(bool(text))
|
||||
|
||||
def done_handler(self):
|
||||
self.result_ready.emit(*self.result)
|
||||
|
||||
def accept_handler(self):
|
||||
self.result = (True, self.line_edit.text())
|
||||
|
||||
def reject_handler(self):
|
||||
self.result = (False, self.line_edit.text())
|
||||
|
||||
|
||||
class WrapperAddDialog(WrapperEditDialog):
|
||||
def __init__(self, parent=None):
|
||||
super(WrapperAddDialog, self).__init__(parent=parent)
|
||||
self.combo_box = QComboBox(self)
|
||||
self.combo_box.addItem("None", "")
|
||||
self.combo_box.currentIndexChanged.connect(self.__on_index_changed)
|
||||
self.widget_layout.insertWidget(0, self.combo_box)
|
||||
|
||||
def setup(self, wrappers: Iterable[Wrapper]):
|
||||
header = self.tr("Add wrapper")
|
||||
self.setWindowTitle(header)
|
||||
self.setSubtitle(header)
|
||||
for wrapper in wrappers:
|
||||
self.combo_box.addItem(f"{wrapper.name} ({wrapper.as_str})", wrapper.as_str)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def __on_index_changed(self, index: int):
|
||||
command = self.combo_box.itemData(index, Qt.UserRole)
|
||||
self.line_edit.setText(command)
|
||||
|
||||
|
||||
class WrapperWidget(QFrame):
|
||||
# object: current, object: new
|
||||
update_wrapper = pyqtSignal(object, object)
|
||||
# object: current
|
||||
delete_wrapper = pyqtSignal(object)
|
||||
|
||||
def __init__(self, wrapper: Wrapper, parent=None):
|
||||
super(WrapperWidget, self).__init__(parent=parent)
|
||||
self.setFrameShape(QFrame.StyledPanel)
|
||||
self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
|
||||
self.setToolTip(wrapper.as_str)
|
||||
|
||||
text_lbl = QLabel(wrapper.name, parent=self)
|
||||
text_lbl.setFont(QFont("monospace"))
|
||||
text_lbl.setEnabled(wrapper.is_editable)
|
||||
|
||||
image_lbl = QLabel(parent=self)
|
||||
image_lbl.setPixmap(icon("mdi.drag-vertical").pixmap(QSize(20, 20)))
|
||||
|
||||
edit_action = QAction("Edit", parent=self)
|
||||
edit_action.triggered.connect(self.__on_edit)
|
||||
delete_action = QAction("Delete", parent=self)
|
||||
delete_action.triggered.connect(self.__on_delete)
|
||||
|
||||
manage_menu = QMenu(parent=self)
|
||||
manage_menu.addActions([edit_action, delete_action])
|
||||
|
||||
manage_button = QToolButton(parent=self)
|
||||
manage_button.setIcon(icon("mdi.menu"))
|
||||
manage_button.setMenu(manage_menu)
|
||||
manage_button.setPopupMode(QToolButton.InstantPopup)
|
||||
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"))
|
||||
|
||||
layout = QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(image_lbl)
|
||||
layout.addWidget(text_lbl)
|
||||
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")
|
||||
|
||||
def data(self) -> Wrapper:
|
||||
return self.wrapper
|
||||
|
||||
@pyqtSlot()
|
||||
def __on_delete(self) -> None:
|
||||
self.delete_wrapper.emit(self.wrapper)
|
||||
self.deleteLater()
|
||||
|
||||
@pyqtSlot()
|
||||
def __on_edit(self) -> None:
|
||||
dialog = WrapperEditDialog(self)
|
||||
dialog.setup(self.wrapper)
|
||||
dialog.result_ready.connect(self.__on_edit_result)
|
||||
dialog.show()
|
||||
|
||||
@pyqtSlot(bool, str)
|
||||
def __on_edit_result(self, accepted: bool, command: str):
|
||||
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)
|
||||
drag.exec_(Qt.MoveAction)
|
||||
|
||||
|
||||
class WrapperSettings(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super(WrapperSettings, self).__init__(parent=parent)
|
||||
self.widget_stack = QStackedWidget(self)
|
||||
|
||||
self.wrapper_scroll = QScrollArea(self.widget_stack)
|
||||
self.wrapper_scroll.setSizeAdjustPolicy(QScrollArea.AdjustToContents)
|
||||
self.wrapper_scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.wrapper_scroll.setWidgetResizable(True)
|
||||
self.wrapper_scroll.setProperty("no_kinetic_scroll", True)
|
||||
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.no_wrapper_label = QLabel(self.tr("No wrappers defined"), self.widget_stack)
|
||||
|
||||
self.widget_stack.addWidget(self.wrapper_scroll)
|
||||
self.widget_stack.addWidget(self.no_wrapper_label)
|
||||
|
||||
self.add_button = QPushButton(self.tr("Add wrapper"), self)
|
||||
self.add_button.clicked.connect(self.__on_add)
|
||||
|
||||
self.wrapper_scroll.horizontalScrollBar().rangeChanged.connect(self.adjust_scrollarea)
|
||||
|
||||
# lk: set object names for the stylesheet
|
||||
self.setObjectName("WrapperSettings")
|
||||
self.no_wrapper_label.setObjectName(f"{self.objectName()}Label")
|
||||
self.wrapper_scroll.setObjectName(f"{self.objectName()}Scroll")
|
||||
self.wrapper_scroll.horizontalScrollBar().setObjectName(
|
||||
f"{self.wrapper_scroll.objectName()}Bar")
|
||||
self.wrapper_scroll.verticalScrollBar().setObjectName(
|
||||
f"{self.wrapper_scroll.objectName()}Bar")
|
||||
|
||||
main_layout = QHBoxLayout(self)
|
||||
main_layout.addWidget(self.widget_stack)
|
||||
main_layout.addWidget(self.add_button, alignment=Qt.AlignTop)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
||||
self.app_name: str = "default"
|
||||
self.core = RareCore.instance().core()
|
||||
self.wrappers = RareCore.instance().wrappers()
|
||||
|
||||
def showEvent(self, a0: QShowEvent):
|
||||
if a0.spontaneous():
|
||||
return super().showEvent(a0)
|
||||
self.update_state()
|
||||
return super().showEvent(a0)
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
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 maxh > minh:
|
||||
self.wrapper_scroll.setMaximumHeight(
|
||||
wrapper_widget.sizeHint().height()
|
||||
+ self.wrapper_scroll.rect().height() // 2
|
||||
- self.wrapper_scroll.contentsRect().height() // 2
|
||||
+ self.wrapper_container.layout().spacing()
|
||||
+ self.wrapper_scroll.horizontalScrollBar().sizeHint().height()
|
||||
)
|
||||
else:
|
||||
self.wrapper_scroll.setMaximumHeight(
|
||||
wrapper_widget.sizeHint().height()
|
||||
+ self.wrapper_scroll.rect().height()
|
||||
- self.wrapper_scroll.contentsRect().height()
|
||||
)
|
||||
|
||||
@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)
|
||||
|
||||
@pyqtSlot()
|
||||
def __on_add(self) -> None:
|
||||
dialog = WrapperAddDialog(self)
|
||||
dialog.setup(self.wrappers.user_wrappers)
|
||||
dialog.result_ready.connect(self.__on_add_result)
|
||||
dialog.show()
|
||||
|
||||
@pyqtSlot(bool, str)
|
||||
def __on_add_result(self, accepted: bool, command: str):
|
||||
if accepted and command:
|
||||
wrapper = Wrapper(shlex.split(command))
|
||||
self.add_user_wrapper(wrapper)
|
||||
|
||||
def __add_wrapper(self, wrapper: Wrapper, position: int = -1):
|
||||
self.widget_stack.setCurrentWidget(self.wrapper_scroll)
|
||||
widget = WrapperWidget(wrapper, self.wrapper_container)
|
||||
if position < 0:
|
||||
self.wrapper_container.addWidget(widget)
|
||||
else:
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
def add_user_wrapper(self, wrapper: Wrapper, position: int = -1):
|
||||
if not wrapper:
|
||||
return
|
||||
|
||||
if pf.system() in {"Linux", "FreeBSD"}:
|
||||
compat_cmds = [tool.command() for tool in steam.find_tools()]
|
||||
if wrapper.as_str in compat_cmds:
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
self.tr("Warning"),
|
||||
self.tr("Do not insert compatibility tools manually. Add them through Proton settings"),
|
||||
)
|
||||
return
|
||||
|
||||
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.as_str)
|
||||
)
|
||||
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.no_wrapper_label.sizeHint().height())
|
||||
self.widget_stack.setCurrentWidget(self.no_wrapper_label)
|
||||
|
||||
@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()
|
||||
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.no_wrapper_label.sizeHint().height())
|
||||
self.widget_stack.setCurrentWidget(self.no_wrapper_label)
|
||||
else:
|
||||
self.widget_stack.setCurrentWidget(self.wrapper_scroll)
|
||||
for wrapper in wrappers:
|
||||
self.__add_wrapper(wrapper)
|
||||
|
||||
|
||||
class WrapperContainer(QWidget):
|
||||
# QWidget: moving widget, int: new index
|
||||
orderChanged: pyqtSignal = pyqtSignal(QWidget, int)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(WrapperContainer, self).__init__(parent=parent)
|
||||
self.setAcceptDrops(True)
|
||||
self.__layout = QHBoxLayout(self)
|
||||
self.__layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.__layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
|
||||
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
|
||||
e.accept()
|
||||
|
||||
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:
|
||||
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:
|
||||
return drag_idx + 1
|
||||
|
||||
return drag_idx
|
||||
|
||||
def dragMoveEvent(self, e: QDragMoveEvent) -> None:
|
||||
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()
|
||||
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()
|
|
@ -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")
|
||||
|
@ -57,12 +58,12 @@ class TrayIcon(QSystemTrayIcon):
|
|||
def last_played(self) -> List:
|
||||
last_played = [game for game in self.rcore.games if (game.metadata and game.is_installed)]
|
||||
last_played.sort(key=lambda g: g.metadata.last_played, reverse=True)
|
||||
return last_played[0:5]
|
||||
return last_played[:5]
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def notify(self, title: str, body: str):
|
||||
if self.settings.value("notification", True, bool):
|
||||
self.showMessage(f"{QApplication.applicationName()} - {title}", body, QSystemTrayIcon.Information, 4000)
|
||||
if self.settings.value(*options.notification):
|
||||
self.showMessage(f"{title} - {QApplication.applicationName()}", body, QSystemTrayIcon.Information, 4000)
|
||||
|
||||
@pyqtSlot()
|
||||
def update_actions(self):
|
||||
|
|
|
@ -90,10 +90,7 @@ class LgndrIndirectStatus:
|
|||
message: str = ""
|
||||
|
||||
def __len__(self):
|
||||
if self.message:
|
||||
return 2
|
||||
else:
|
||||
return 0
|
||||
return 2 if self.message else 0
|
||||
|
||||
def __bool__(self):
|
||||
return self.success
|
||||
|
@ -128,7 +125,7 @@ class LgndrIndirectLogger:
|
|||
self.level = level
|
||||
|
||||
def _log(self, level: int, msg: str):
|
||||
self.status.success = True if level < self.level else False
|
||||
self.status.success = level < self.level
|
||||
self.status.message = msg
|
||||
if self.logger:
|
||||
self.logger.log(level, msg)
|
||||
|
|
46
rare/main.py
46
rare/main.py
|
@ -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"])
|
||||
|
||||
|
@ -57,22 +59,26 @@ def main() -> int:
|
|||
)
|
||||
subparsers = parser.add_subparsers(title="Commands", dest="subparser")
|
||||
|
||||
launch_minimal_parser = subparsers.add_parser("start", aliases=["launch"])
|
||||
launch_minimal_parser.add_argument("app_name", help="AppName of the game to launch",
|
||||
metavar="<App Name>", action="store")
|
||||
launch_minimal_parser.add_argument("--dry-run", help="Print arguments and exit", action="store_true")
|
||||
launch_minimal_parser.add_argument("--offline", help="Launch game offline",
|
||||
action="store_true")
|
||||
launch_minimal_parser.add_argument('--wine-bin', dest='wine_bin', action='store', metavar='<wine binary>',
|
||||
default=os.environ.get('LGDRY_WINE_BINARY', None),
|
||||
help='Set WINE binary to use to launch the app')
|
||||
launch_minimal_parser.add_argument('--wine-prefix', dest='wine_pfx', action='store', metavar='<wine pfx path>',
|
||||
default=os.environ.get('LGDRY_WINE_PREFIX', None),
|
||||
help='Set WINE prefix to use')
|
||||
launch_minimal_parser.add_argument("--ask-sync-saves", help="Ask to sync cloud saves",
|
||||
action="store_true")
|
||||
launch_minimal_parser.add_argument("--skip-update-check", help="Do not check for updates",
|
||||
action="store_true")
|
||||
launch_parser = subparsers.add_parser("launch", aliases=["start"])
|
||||
launch_parser.add_argument("app_name", help="AppName of the game to launch",
|
||||
metavar="<App Name>", action="store")
|
||||
launch_parser.add_argument("--dry-run", help="Print arguments and exit", action="store_true")
|
||||
launch_parser.add_argument("--offline", help="Launch game offline",
|
||||
action="store_true")
|
||||
launch_parser.add_argument('--wine-bin', dest='wine_bin', action='store', metavar='<wine binary>',
|
||||
default=os.environ.get('LGDRY_WINE_BINARY', None),
|
||||
help='Set WINE binary to use to launch the app')
|
||||
launch_parser.add_argument('--wine-prefix', dest='wine_pfx', action='store', metavar='<wine pfx path>',
|
||||
default=os.environ.get('LGDRY_WINE_PREFIX', None),
|
||||
help='Set WINE prefix to use')
|
||||
launch_parser.add_argument("--ask-sync-saves", help="Ask to sync cloud saves",
|
||||
action="store_true")
|
||||
launch_parser.add_argument("--skip-update-check", help="Do not check for updates",
|
||||
action="store_true")
|
||||
|
||||
login_parser = subparsers.add_parser("login", aliases=["auth"])
|
||||
login_parser.add_argument("egl_version", help="Epic Games Launcher User Agent version",
|
||||
metavar="<EGL Version>", action="store")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
@ -93,8 +99,12 @@ def main() -> int:
|
|||
print(f"Rare {__version__} Codename: {__codename__}")
|
||||
return 0
|
||||
|
||||
if args.subparser in {"start", "launch"}:
|
||||
from rare.launcher import launch
|
||||
if args.subparser in {"login", "auth"}:
|
||||
from rare.commands.webview import launch
|
||||
return launch(args)
|
||||
|
||||
if args.subparser in {"launch", "start"}:
|
||||
from rare.commands.launcher import launch
|
||||
return launch(args)
|
||||
|
||||
from rare.utils import singleton
|
||||
|
|
|
@ -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,6 +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 set_envvar
|
||||
|
||||
logger = getLogger("RareGame")
|
||||
|
||||
|
@ -26,11 +27,11 @@ 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
|
||||
tags: List[str] = field(default_factory=list)
|
||||
|
@ -38,23 +39,24 @@ 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,
|
||||
tags=self.tags,
|
||||
|
@ -77,6 +79,7 @@ class RareGame(RareGameSlim):
|
|||
self.pixmap: QPixmap = QPixmap()
|
||||
self.metadata: RareGame.Metadata = RareGame.Metadata()
|
||||
self.__load_metadata()
|
||||
self.grant_date()
|
||||
|
||||
self.owned_dlcs: Set[RareGame] = set()
|
||||
|
||||
|
@ -137,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
|
||||
|
@ -160,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(
|
||||
|
@ -337,8 +341,7 @@ class RareGame(RareGameSlim):
|
|||
"""
|
||||
if self.igame is not None:
|
||||
return self.igame.needs_verification
|
||||
else:
|
||||
return False
|
||||
return False
|
||||
|
||||
@needs_verification.setter
|
||||
def needs_verification(self, needs: bool) -> None:
|
||||
|
@ -426,19 +429,28 @@ class RareGame(RareGameSlim):
|
|||
def steam_grade(self) -> str:
|
||||
if platform.system() == "Windows" or self.is_unreal:
|
||||
return "na"
|
||||
elapsed_time = abs(datetime.utcnow() - self.metadata.steam_date)
|
||||
if self.metadata.steam_grade is not None and elapsed_time.days < 3:
|
||||
return self.metadata.steam_grade
|
||||
if self.metadata.steam_grade != "pending":
|
||||
elapsed_time = abs(datetime.utcnow() - self.metadata.steam_date)
|
||||
|
||||
def _set_steam_grade():
|
||||
rating = get_rating(self.core, self.app_name)
|
||||
self.set_steam_grade(rating)
|
||||
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)
|
||||
self.metadata.steam_grade = "pending"
|
||||
return self.metadata.steam_grade
|
||||
|
||||
worker = QRunnable.create(_set_steam_grade)
|
||||
QThreadPool.globalInstance().start(worker)
|
||||
return "pending"
|
||||
@property
|
||||
def steam_appid(self) -> Optional[int]:
|
||||
return self.metadata.steam_appid
|
||||
|
||||
def set_steam_grade(self, grade: str) -> None:
|
||||
def set_steam_grade(self, appid: int, grade: str) -> None:
|
||||
if appid and self.steam_appid is None:
|
||||
set_envvar(self.app_name, "SteamAppId", str(appid))
|
||||
set_envvar(self.app_name, "SteamGameId", str(appid))
|
||||
set_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()
|
||||
self.__save_metadata()
|
||||
|
@ -446,17 +458,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
|
||||
|
@ -474,9 +486,7 @@ class RareGame(RareGameSlim):
|
|||
if self.is_installed:
|
||||
if (not self.is_idle) or self.needs_verification:
|
||||
return False
|
||||
if self.is_foreign and not self.can_run_offline:
|
||||
return False
|
||||
return True
|
||||
return bool(not self.is_foreign or self.can_run_offline)
|
||||
return False
|
||||
|
||||
def get_pixmap(self, color=True) -> QPixmap:
|
||||
|
@ -622,7 +632,7 @@ class RareEosOverlay(RareGameBase):
|
|||
reg_paths = eos.query_registry_entries(prefix)
|
||||
if old_path := reg_paths["overlay_path"]:
|
||||
if os.path.normpath(old_path) == path:
|
||||
logger.info(f"Overlay already enabled, nothing to do.")
|
||||
logger.info("Overlay already enabled, nothing to do.")
|
||||
return True
|
||||
else:
|
||||
logger.info(f'Updating overlay registry entries from "{old_path}" to "{path}"')
|
||||
|
|
|
@ -21,14 +21,9 @@ class ImageSize:
|
|||
self.__img_factor = 17
|
||||
self.__size = QSize(self.__img_factor * 16, self.__img_factor * 9) * pixel_ratio / divisor
|
||||
# lk: for prettier images set this to true
|
||||
self.__smooth_transform: bool = True
|
||||
if divisor > 2:
|
||||
self.__smooth_transform = False
|
||||
|
||||
if base is not None:
|
||||
self.__base = base
|
||||
else:
|
||||
self.__base = self
|
||||
# self.__smooth_transform: bool = True
|
||||
self.__smooth_transform = divisor <= 2
|
||||
self.__base = base if base is not None else self
|
||||
|
||||
def __eq__(self, other: 'ImageSize.Preset'):
|
||||
return (
|
||||
|
|
24
rare/models/library.py
Normal file
24
rare/models/library.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
from enum import IntEnum
|
||||
|
||||
|
||||
class LibraryView(IntEnum):
|
||||
COVER = 1
|
||||
VLIST = 2
|
||||
|
||||
|
||||
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
|
56
rare/models/options.py
Normal file
56
rare/models/options.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
import locale
|
||||
import platform as pf
|
||||
from argparse import Namespace
|
||||
from typing import Any, Type
|
||||
from .library import LibraryFilter, LibraryOrder, LibraryView
|
||||
|
||||
|
||||
class Value(Namespace):
|
||||
key: str
|
||||
default: Any
|
||||
dtype: Type
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__dict__)
|
||||
|
||||
def __iter__(self):
|
||||
yield from self.__dict__.values()
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
language = Value(key="language", default=locale.getlocale()[0], dtype=str)
|
||||
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)
|
||||
|
||||
color_scheme = Value(key="color_scheme", default="", dtype=str)
|
||||
style_sheet = Value(key="style_sheet", default="RareStyle", dtype=str)
|
||||
|
||||
library_view = Value(key="library_view", default=int(LibraryView.COVER), dtype=int)
|
||||
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', 'LibraryFilter', 'LibraryOrder', 'LibraryView']
|
|
@ -1,47 +1,70 @@
|
|||
import os
|
||||
from typing import Union, List
|
||||
from typing import Union, List, LiteralString
|
||||
|
||||
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 = [
|
||||
prefix
|
||||
for prefix, _ in possible_prefixes
|
||||
if os.path.exists(os.path.join(prefix, PathSpec.wine_egl_programdata()))
|
||||
]
|
||||
prefixes = []
|
||||
for prefix in possible_prefixes:
|
||||
if os.path.exists(os.path.join(prefix, self.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["{epicid}"] = core.lgd.userdata["account_id"]
|
||||
|
||||
if igame is not None:
|
||||
self.__egl_path_vars["{installdir}"] = igame.install_path
|
||||
|
||||
def resolve_egl_path_vars(self, path: str) -> Union[LiteralString, str, bytes]:
|
||||
cooked_path = (self.__egl_path_vars.get(p.lower(), p) for p in path.split("/"))
|
||||
return os.path.join(*cooked_path)
|
||||
|
|
|
@ -14,11 +14,11 @@ class GlobalSignals:
|
|||
# str: title, str: body
|
||||
notify = pyqtSignal(str, str)
|
||||
# none
|
||||
prefix_updated = pyqtSignal()
|
||||
# none
|
||||
update_tray = pyqtSignal()
|
||||
# none
|
||||
update_statusbar = pyqtSignal()
|
||||
# str: locale
|
||||
# change_translation = pyqtSignal(str)
|
||||
|
||||
class GameSignals(QObject):
|
||||
# model
|
||||
|
|
77
rare/models/wrapper.py
Normal file
77
rare/models/wrapper.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
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 in {WrapperType.USER_DEFINED, WrapperType.LEGENDARY_IMPORT}
|
||||
|
||||
@property
|
||||
def checksum(self) -> str:
|
||||
return md5(self.as_str.encode("utf-8")).hexdigest()
|
||||
|
||||
@property
|
||||
def executable(self) -> str:
|
||||
return shlex.quote(self.__command[0])
|
||||
|
||||
@property
|
||||
def command(self) -> List[str]:
|
||||
return self.__command
|
||||
|
||||
@property
|
||||
def as_str(self) -> str:
|
||||
# return " ".join(shlex.quote(part) for part in self.__command)
|
||||
return " ".join(map(shlex.quote, 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.as_str == other.as_str
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__command)
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return True if not self.is_editable else bool(self.as_str.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)
|
||||
)
|
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load diff
|
@ -73,6 +73,7 @@ class GameProcess(QObject):
|
|||
).format(self.game.app_name)
|
||||
)
|
||||
self.timer.stop()
|
||||
self.tried_connections = 0
|
||||
self.finished.emit(GameProcess.Code.TIMEOUT)
|
||||
|
||||
@pyqtSlot()
|
||||
|
|
|
@ -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()
|
||||
|
@ -100,13 +102,17 @@ class RareCore(QObject):
|
|||
self.__signals.application.update_statusbar.emit()
|
||||
|
||||
def active_workers(self) -> Iterable[QueueWorker]:
|
||||
return list(filter(lambda w: w.state == QueueWorkerState.ACTIVE, self.queue_workers))
|
||||
# return list(filter(lambda w: w.state == QueueWorkerState.ACTIVE, self.queue_workers))
|
||||
yield from filter(lambda w: w.state == QueueWorkerState.ACTIVE, self.queue_workers)
|
||||
|
||||
def queued_workers(self) -> Iterable[QueueWorker]:
|
||||
return list(filter(lambda w: w.state == QueueWorkerState.QUEUED, self.queue_workers))
|
||||
# return list(filter(lambda w: w.state == QueueWorkerState.QUEUED, self.queue_workers))
|
||||
yield from filter(lambda w: w.state == QueueWorkerState.QUEUED, self.queue_workers)
|
||||
|
||||
def queue_info(self) -> List[QueueWorkerInfo]:
|
||||
return [w.worker_info() for w in self.queue_workers]
|
||||
def queue_info(self) -> Iterable[QueueWorkerInfo]:
|
||||
# return (w.worker_info() for w in self.queue_workers)
|
||||
for w in self.queue_workers:
|
||||
yield w.worker_info()
|
||||
|
||||
@staticmethod
|
||||
def instance() -> 'RareCore':
|
||||
|
@ -205,6 +211,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 +340,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 +381,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,13 +33,13 @@ 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:
|
||||
if want_entitlements and not self.args.offline:
|
||||
# Get entitlements, Ubisoft integration also uses them
|
||||
self.signals.progress.emit(0, self.signals.tr("Updating entitlements"))
|
||||
with timelogger(logger, "Request 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:
|
||||
|
|
|
@ -58,7 +58,8 @@ class InstallInfoWorker(Worker):
|
|||
if not download.res or not download.res.failures:
|
||||
self.signals.result.emit(download)
|
||||
else:
|
||||
self.signals.failed.emit("\n".join(str(i) for i in download.res.failures))
|
||||
# self.signals.failed.emit("\n".join(str(i) for i in download.res.failures))
|
||||
self.signals.failed.emit("\n".join(map(str, download.res.failures)))
|
||||
except LgndrException as ret:
|
||||
self.signals.failed.emit(ret.message)
|
||||
except Exception as e:
|
||||
|
|
|
@ -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,12 +31,12 @@ 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
|
||||
if prefixes is not None:
|
||||
for prefix in prefixes:
|
||||
if len(prefixes):
|
||||
for prefix, _ in prefixes:
|
||||
remove_registry_entries(prefix)
|
||||
logger.debug("Removed registry entries for prefix %s", prefix)
|
||||
else:
|
||||
|
@ -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,63 +3,113 @@ import platform
|
|||
import time
|
||||
from configparser import ConfigParser
|
||||
from logging import getLogger
|
||||
from typing import Union, Iterable
|
||||
from typing import Union, Iterable, List, Tuple, Dict
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QObject, QRunnable
|
||||
from PyQt5.QtCore import pyqtSignal, QObject
|
||||
|
||||
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.shared.wrappers import Wrappers
|
||||
from rare.utils import config_helper as config
|
||||
from rare.utils.misc import path_size, format_size
|
||||
from .worker import Worker
|
||||
|
||||
if platform.system() == "Windows":
|
||||
# noinspection PyUnresolvedReferences
|
||||
import winreg # pylint: disable=E0401
|
||||
import winreg # pylint: disable=E0401
|
||||
from legendary.lfs import windows_helpers
|
||||
else:
|
||||
from rare.utils.compat import utils as compat_utils, steam
|
||||
|
||||
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, core: LegendaryCore, app_name: str, path: str):
|
||||
super(WinePathResolver, self). __init__()
|
||||
self.signals = WinePathResolver.Signals()
|
||||
self.core = core
|
||||
self.app_name = app_name
|
||||
self.path = path
|
||||
|
||||
@staticmethod
|
||||
def _configure_process(core: LegendaryCore, app_name: str) -> Tuple[List, Dict]:
|
||||
tool: steam.CompatibilityTool = None
|
||||
|
||||
if config.get_boolean(app_name, "no_wine"):
|
||||
wrappers = Wrappers()
|
||||
for w in wrappers.get_game_wrapper_list(app_name):
|
||||
if w.is_compat_tool:
|
||||
for t in steam.find_tools():
|
||||
if t.checksum == w.checksum:
|
||||
tool = t
|
||||
break
|
||||
|
||||
cmd = core.get_app_launch_command(
|
||||
app_name,
|
||||
wrapper=tool.as_str(steam.SteamVerb.RUN_IN_PREFIX) if tool is not None else None,
|
||||
disable_wine=config.get_boolean(app_name, "no_wine")
|
||||
)
|
||||
env = core.get_app_environment(app_name, disable_wine=config.get_boolean(app_name, "no_wine"))
|
||||
env = compat_utils.get_host_environment(env, silent=True)
|
||||
|
||||
return cmd, env
|
||||
|
||||
@staticmethod
|
||||
def _resolve_unix_path(cmd, env, path: str) -> str:
|
||||
logger.info("Resolving path '%s'", path)
|
||||
wine_path = compat_utils.resolve_path(cmd, env, path)
|
||||
logger.info("Resolved Wine path '%s'", wine_path)
|
||||
unix_path = compat_utils.convert_to_unix_path(cmd, env, wine_path)
|
||||
logger.info("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("")
|
||||
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)
|
||||
# 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)
|
||||
command, environ = self._configure_process(self.core, self.app_name)
|
||||
if not (command and environ):
|
||||
logger.error("Cannot setup %s, missing infomation", {type(self).__name__})
|
||||
self.signals.result_ready.emit("", self.app_name)
|
||||
|
||||
path = self._resolve_unix_path(command, environ, self.path)
|
||||
self.signals.result_ready.emit(path, self.app_name)
|
||||
return
|
||||
|
||||
|
||||
class OriginWineWorker(QRunnable):
|
||||
class WineSavePathResolver(WinePathResolver):
|
||||
|
||||
def __init__(self, core: LegendaryCore, rgame: RareGame):
|
||||
path = PathSpec(core, rgame.igame).resolve_egl_path_vars(rgame.raw_save_path)
|
||||
super(WineSavePathResolver, self).__init__(rgame.core, rgame.app_name, str(path))
|
||||
self.rgame = rgame
|
||||
|
||||
def run_real(self):
|
||||
logger.info("Resolving save path for %s (%s)", self.rgame.app_title, self.rgame.app_name)
|
||||
command, environ = self._configure_process(self.core, self.rgame.app_name)
|
||||
if not (command and environ):
|
||||
logger.error("Cannot setup %s, missing infomation", {type(self).__name__})
|
||||
self.signals.result_ready.emit("", self.rgame.app_name)
|
||||
|
||||
path = self._resolve_unix_path(command, environ, self.path)
|
||||
# Clean wine output
|
||||
# pylint: disable=E1136
|
||||
if os.path.exists(path):
|
||||
self.rgame.save_path = path
|
||||
self.signals.result_ready.emit(path, self.rgame.app_name)
|
||||
return
|
||||
|
||||
|
||||
class OriginWineWorker(WinePathResolver):
|
||||
def __init__(self, core: LegendaryCore, games: Union[Iterable[RareGame], RareGame]):
|
||||
super(OriginWineWorker, self).__init__()
|
||||
super(OriginWineWorker, self).__init__(core, "", "")
|
||||
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:
|
||||
def run_real(self) -> None:
|
||||
t = time.time()
|
||||
|
||||
for rgame in self.games:
|
||||
|
@ -79,15 +129,17 @@ 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, environ = self._configure_process(self.core, rgame.app_name)
|
||||
|
||||
use_wine = False
|
||||
prefix = config.get_prefix(rgame.app_name)
|
||||
if not prefix:
|
||||
return
|
||||
|
||||
use_wine = True
|
||||
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 compat_utils.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 +148,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 = compat_utils.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 = compat_utils.convert_to_unix_path(command, environ, install_dir)
|
||||
if install_dir:
|
||||
logger.debug("Found Unix install directory %s", install_dir)
|
||||
else:
|
||||
|
|
165
rare/shared/wrappers.py
Normal file
165
rare/shared/wrappers.py
Normal file
|
@ -0,0 +1,165 @@
|
|||
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.as_str 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)
|
||||
config.save_option(app_name, "wrapper", command_string)
|
||||
|
||||
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.compat import steam
|
||||
|
||||
global config_dir
|
||||
config_dir = os.getcwd
|
||||
global config
|
||||
config = Namespace()
|
||||
config.set_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(steam.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.as_str)
|
||||
|
||||
for wrp in wr.user_wrappers:
|
||||
pprint(wrp)
|
|
@ -133,6 +133,7 @@ class Ui_GameInfo(object):
|
|||
self.info_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.lbl_grade)
|
||||
self.grade = QtWidgets.QLabel(GameInfo)
|
||||
self.grade.setText("error")
|
||||
self.grade.setOpenExternalLinks(True)
|
||||
self.grade.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse|QtCore.Qt.TextSelectableByMouse)
|
||||
self.grade.setObjectName("grade")
|
||||
self.info_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.grade)
|
||||
|
|
|
@ -232,6 +232,9 @@
|
|||
<property name="text">
|
||||
<string notr="true">error</string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
|
|
|
@ -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,117 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GameSettings</class>
|
||||
<widget class="QWidget" name="GameSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>483</width>
|
||||
<height>154</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>GameSettings</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="game_settings_layout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="launch_settings_group">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Launch Settings</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="launch_settings_layout">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="skip_update_label">
|
||||
<property name="text">
|
||||
<string>Skip update check</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="skip_update">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Default</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Yes</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>No</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="offline_label">
|
||||
<property name="text">
|
||||
<string>Offline mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="offline">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Default</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Yes</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>No</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="launch_params_label">
|
||||
<property name="text">
|
||||
<string>Launch parameters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="launch_params">
|
||||
<property name="placeholderText">
|
||||
<string>parameters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -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>
|
|
@ -1,58 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/proton.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.15.6
|
||||
#
|
||||
# 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_ProtonSettings(object):
|
||||
def setupUi(self, ProtonSettings):
|
||||
ProtonSettings.setObjectName("ProtonSettings")
|
||||
ProtonSettings.resize(190, 86)
|
||||
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.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.proton_combo = QtWidgets.QComboBox(ProtonSettings)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.proton_combo.sizePolicy().hasHeightForWidth())
|
||||
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.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.retranslateUi(ProtonSettings)
|
||||
|
||||
def retranslateUi(self, ProtonSettings):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
ProtonSettings.setTitle(_translate("ProtonSettings", "Proton Settings"))
|
||||
self.proton_wrapper_label.setText(_translate("ProtonSettings", "Proton"))
|
||||
self.proton_combo.setItemText(0, _translate("ProtonSettings", "Don\'t use Proton"))
|
||||
self.proton_prefix_label.setText(_translate("ProtonSettings", "Prefix"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
ProtonSettings = QtWidgets.QGroupBox()
|
||||
ui = Ui_ProtonSettings()
|
||||
ui.setupUi(ProtonSettings)
|
||||
ProtonSettings.show()
|
||||
sys.exit(app.exec_())
|
|
@ -1,59 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ProtonSettings</class>
|
||||
<widget class="QGroupBox" name="ProtonSettings">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>190</width>
|
||||
<height>86</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">ProtonSettings</string>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Proton Settings</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="proton_settings_layout">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="proton_wrapper_label">
|
||||
<property name="text">
|
||||
<string>Proton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="proton_combo">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Don't use Proton</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="proton_prefix_label">
|
||||
<property name="text">
|
||||
<string>Prefix</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="prefix_layout"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue