Rare: pass through Black formatter
This commit is contained in:
parent
0d2f36e028
commit
8f89eb6e88
|
@ -10,6 +10,7 @@ def main():
|
|||
|
||||
# fix cx_freeze
|
||||
import multiprocessing
|
||||
|
||||
multiprocessing.freeze_support()
|
||||
|
||||
# insert legendary for installed via pip/setup.py submodule to path
|
||||
|
@ -18,39 +19,61 @@ def main():
|
|||
|
||||
# CLI Options
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("-V", "--version", action="store_true", help="Shows version and exits")
|
||||
parser.add_argument("-S", "--silent", action="store_true",
|
||||
help="Launch Rare in background. Open it from System Tray Icon")
|
||||
parser.add_argument(
|
||||
"-V", "--version", action="store_true", help="Shows version and exits"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-S",
|
||||
"--silent",
|
||||
action="store_true",
|
||||
help="Launch Rare in background. Open it from System Tray Icon",
|
||||
)
|
||||
parser.add_argument("--debug", action="store_true", help="Launch in debug mode")
|
||||
parser.add_argument("--offline", action="store_true", help="Launch Rare in offline mode")
|
||||
parser.add_argument("--test-start", action="store_true", help="Quit immediately after launch")
|
||||
parser.add_argument(
|
||||
"--offline", action="store_true", help="Launch Rare in offline mode"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--test-start", action="store_true", help="Quit immediately after launch"
|
||||
)
|
||||
|
||||
parser.add_argument("--desktop-shortcut", action="store_true", dest="desktop_shortcut",
|
||||
help="Use this, if there is no link on desktop to start Rare")
|
||||
parser.add_argument("--startmenu-shortcut", action="store_true", dest="startmenu_shortcut",
|
||||
help="Use this, if there is no link in start menu to launch Rare")
|
||||
parser.add_argument(
|
||||
"--desktop-shortcut",
|
||||
action="store_true",
|
||||
dest="desktop_shortcut",
|
||||
help="Use this, if there is no link on desktop to start Rare",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--startmenu-shortcut",
|
||||
action="store_true",
|
||||
dest="startmenu_shortcut",
|
||||
help="Use this, if there is no link in start menu to launch Rare",
|
||||
)
|
||||
subparsers = parser.add_subparsers(title="Commands", dest="subparser")
|
||||
|
||||
launch_parser = subparsers.add_parser("launch")
|
||||
launch_parser.add_argument('app_name', help='Name of the app', metavar='<App Name>')
|
||||
launch_parser.add_argument("app_name", help="Name of the app", metavar="<App Name>")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.desktop_shortcut:
|
||||
from rare.utils import utils
|
||||
|
||||
utils.create_rare_desktop_link("desktop")
|
||||
print("Link created")
|
||||
|
||||
if args.startmenu_shortcut:
|
||||
from rare.utils import utils
|
||||
|
||||
utils.create_rare_desktop_link("start_menu")
|
||||
print("link created")
|
||||
|
||||
if args.version:
|
||||
from rare import __version__, code_name
|
||||
|
||||
print(f"Rare {__version__} Codename: {code_name}")
|
||||
return
|
||||
from rare.utils import singleton
|
||||
|
||||
try:
|
||||
# this object only allows one instance per machine
|
||||
|
||||
|
@ -58,6 +81,7 @@ def main():
|
|||
except singleton.SingleInstanceException:
|
||||
print("Rare is already running")
|
||||
from rare import data_dir
|
||||
|
||||
with open(os.path.join(data_dir, "lockfile"), "w") as file:
|
||||
if args.subparser == "launch":
|
||||
file.write("launch " + args.app_name)
|
||||
|
@ -70,13 +94,16 @@ def main():
|
|||
args.silent = True
|
||||
|
||||
from rare.app import start
|
||||
|
||||
start(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
# run from source
|
||||
# insert raw legendary submodule
|
||||
sys.path.insert(0, os.path.join(pathlib.Path(__file__).parent.absolute(), "legendary"))
|
||||
sys.path.insert(
|
||||
0, os.path.join(pathlib.Path(__file__).parent.absolute(), "legendary")
|
||||
)
|
||||
# insert source directory
|
||||
sys.path.insert(0, str(pathlib.Path(__file__).parents[1].absolute()))
|
||||
|
||||
|
|
88
rare/app.py
88
rare/app.py
|
@ -12,6 +12,7 @@ from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox
|
|||
from requests import HTTPError
|
||||
|
||||
import legendary
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
import rare.resources.resources
|
||||
import rare.shared as shared
|
||||
|
@ -21,7 +22,7 @@ from rare.components.main_window import MainWindow
|
|||
from rare.components.tray_icon import TrayIcon
|
||||
from rare.utils.utils import set_color_pallete, set_style_sheet
|
||||
|
||||
start_time = time.strftime('%y-%m-%d--%H-%M') # year-month-day-hour-minute
|
||||
start_time = time.strftime("%y-%m-%d--%H-%M") # year-month-day-hour-minute
|
||||
file_name = os.path.join(cache_dir, "logs", f"Rare_{start_time}.log")
|
||||
if not os.path.exists(os.path.dirname(file_name)):
|
||||
os.makedirs(os.path.dirname(file_name))
|
||||
|
@ -63,10 +64,10 @@ class App(QApplication):
|
|||
self.core = shared.init_legendary()
|
||||
except configparser.MissingSectionHeaderError as e:
|
||||
logger.warning(f"Config is corrupt: {e}")
|
||||
if config_path := os.environ.get('XDG_CONFIG_HOME'):
|
||||
path = os.path.join(config_path, 'legendary')
|
||||
if config_path := os.environ.get("XDG_CONFIG_HOME"):
|
||||
path = os.path.join(config_path, "legendary")
|
||||
else:
|
||||
path = os.path.expanduser('~/.config/legendary')
|
||||
path = os.path.expanduser("~/.config/legendary")
|
||||
with open(os.path.join(path, "config.ini"), "w") as config_file:
|
||||
config_file.write("[Legendary]")
|
||||
self.core = shared.init_legendary()
|
||||
|
@ -95,12 +96,15 @@ class App(QApplication):
|
|||
|
||||
self.signals.exit_app.connect(self.exit_app)
|
||||
self.signals.send_notification.connect(
|
||||
lambda title:
|
||||
self.tray_icon.showMessage(
|
||||
lambda title: self.tray_icon.showMessage(
|
||||
self.tr("Download finished"),
|
||||
self.tr("Download finished. {} is playable now").format(title),
|
||||
QSystemTrayIcon.Information, 4000)
|
||||
if self.settings.value("notification", True, bool) else None)
|
||||
QSystemTrayIcon.Information,
|
||||
4000,
|
||||
)
|
||||
if self.settings.value("notification", True, bool)
|
||||
else None
|
||||
)
|
||||
|
||||
# Translator
|
||||
self.translator = QTranslator()
|
||||
|
@ -122,9 +126,12 @@ class App(QApplication):
|
|||
# Style
|
||||
# lk: this is a bit silly but serves well until we have a class
|
||||
# lk: store the default qt style name from the system on startup as a property for later reference
|
||||
self.setProperty('rareDefaultQtStyle', self.style().objectName())
|
||||
self.setProperty("rareDefaultQtStyle", self.style().objectName())
|
||||
|
||||
if self.settings.value("color_scheme", None) is None and self.settings.value("style_sheet", None) is None:
|
||||
if (
|
||||
self.settings.value("color_scheme", None) is None
|
||||
and self.settings.value("style_sheet", None) is None
|
||||
):
|
||||
self.settings.setValue("color_scheme", "")
|
||||
self.settings.setValue("style_sheet", "RareStyle")
|
||||
|
||||
|
@ -157,23 +164,38 @@ class App(QApplication):
|
|||
self.tray_icon = TrayIcon(self)
|
||||
self.tray_icon.exit_action.triggered.connect(self.exit_app)
|
||||
self.tray_icon.start_rare.triggered.connect(self.show_mainwindow)
|
||||
self.tray_icon.activated.connect(lambda r: self.show_mainwindow() if r == QSystemTrayIcon.DoubleClick else None)
|
||||
self.tray_icon.activated.connect(
|
||||
lambda r: self.show_mainwindow()
|
||||
if r == QSystemTrayIcon.DoubleClick
|
||||
else None
|
||||
)
|
||||
|
||||
if not self.args.silent:
|
||||
self.mainwindow.show_window_centralized()
|
||||
self.window_launched = True
|
||||
|
||||
if shared.args.subparser == "launch":
|
||||
if shared.args.app_name in [i.app_name for i in self.core.get_installed_list()]:
|
||||
logger.info("Launching " + self.core.get_installed_game(shared.args.app_name).title)
|
||||
self.mainwindow.tab_widget.games_tab.game_utils.prepare_launch(shared.args.app_name)
|
||||
if shared.args.app_name in [
|
||||
i.app_name for i in self.core.get_installed_list()
|
||||
]:
|
||||
logger.info(
|
||||
"Launching "
|
||||
+ self.core.get_installed_game(shared.args.app_name).title
|
||||
)
|
||||
self.mainwindow.tab_widget.games_tab.game_utils.prepare_launch(
|
||||
shared.args.app_name
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
f"Could not find {shared.args.app_name} in Games or it is not installed")
|
||||
QMessageBox.warning(self.mainwindow, "Warning",
|
||||
self.tr(
|
||||
"Could not find {} in installed games. Did you modify the shortcut? ").format(
|
||||
shared.args.app_name))
|
||||
f"Could not find {shared.args.app_name} in Games or it is not installed"
|
||||
)
|
||||
QMessageBox.warning(
|
||||
self.mainwindow,
|
||||
"Warning",
|
||||
self.tr(
|
||||
"Could not find {} in installed games. Did you modify the shortcut? "
|
||||
).format(shared.args.app_name),
|
||||
)
|
||||
|
||||
if shared.args.test_start:
|
||||
self.exit_app(0)
|
||||
|
@ -190,8 +212,12 @@ class App(QApplication):
|
|||
question = QMessageBox.question(
|
||||
self.mainwindow,
|
||||
self.tr("Close"),
|
||||
self.tr("There is a download active. Do you really want to exit app?"),
|
||||
QMessageBox.Yes, QMessageBox.No)
|
||||
self.tr(
|
||||
"There is a download active. Do you really want to exit app?"
|
||||
),
|
||||
QMessageBox.Yes,
|
||||
QMessageBox.No,
|
||||
)
|
||||
if question == QMessageBox.No:
|
||||
return
|
||||
else:
|
||||
|
@ -218,19 +244,23 @@ def start(args):
|
|||
|
||||
# configure logging
|
||||
if args.debug:
|
||||
logging.basicConfig(format='[%(name)s] %(levelname)s: %(message)s', level=logging.DEBUG)
|
||||
logging.basicConfig(
|
||||
format="[%(name)s] %(levelname)s: %(message)s", level=logging.DEBUG
|
||||
)
|
||||
logging.getLogger().setLevel(level=logging.DEBUG)
|
||||
# keep requests, asyncio and pillow quiet
|
||||
logging.getLogger('requests').setLevel(logging.WARNING)
|
||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||
logging.getLogger("requests").setLevel(logging.WARNING)
|
||||
logging.getLogger("urllib3").setLevel(logging.WARNING)
|
||||
logging.getLogger("asyncio").setLevel(logging.WARNING)
|
||||
logger.info(f"Launching Rare version {rare.__version__} Codename: {rare.code_name}\n"
|
||||
f"Using Legendary {legendary.__version__} Codename: {legendary.__codename__} as backend\n"
|
||||
f"Operating System: {platform.system()}, Python version: {platform.python_version()}\n"
|
||||
f"Running {sys.executable} {' '.join(sys.argv)}")
|
||||
logger.info(
|
||||
f"Launching Rare version {rare.__version__} Codename: {rare.code_name}\n"
|
||||
f"Using Legendary {legendary.__version__} Codename: {legendary.__codename__} as backend\n"
|
||||
f"Operating System: {platform.system()}, Python version: {platform.python_version()}\n"
|
||||
f"Running {sys.executable} {' '.join(sys.argv)}"
|
||||
)
|
||||
else:
|
||||
logging.basicConfig(
|
||||
format='[%(name)s] %(levelname)s: %(message)s',
|
||||
format="[%(name)s] %(levelname)s: %(message)s",
|
||||
level=logging.INFO,
|
||||
filename=file_name,
|
||||
)
|
||||
|
|
|
@ -18,7 +18,9 @@ from rare.utils.utils import get_size
|
|||
class InstallDialog(QDialog, Ui_InstallDialog):
|
||||
result_ready = pyqtSignal(InstallQueueItemModel)
|
||||
|
||||
def __init__(self, dl_item: InstallQueueItemModel, update=False, silent=False, parent=None):
|
||||
def __init__(
|
||||
self, dl_item: InstallQueueItemModel, update=False, silent=False, parent=None
|
||||
):
|
||||
super(InstallDialog, self).__init__(parent)
|
||||
self.setupUi(self)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose, True)
|
||||
|
@ -40,17 +42,19 @@ class InstallDialog(QDialog, Ui_InstallDialog):
|
|||
self.threadpool.setMaxThreadCount(1)
|
||||
|
||||
header = self.tr("Update") if update else self.tr("Install")
|
||||
self.install_dialog_label.setText(f"<h3>{header} \"{self.game.app_title}\"</h3>")
|
||||
self.setWindowTitle(f"{self.windowTitle()} - {header} \"{self.game.app_title}\"")
|
||||
self.install_dialog_label.setText(f'<h3>{header} "{self.game.app_title}"</h3>')
|
||||
self.setWindowTitle(f'{self.windowTitle()} - {header} "{self.game.app_title}"')
|
||||
|
||||
default_path = os.path.expanduser("~/legendary")
|
||||
if self.core.lgd.config.has_option("Legendary", "install_dir"):
|
||||
default_path = self.core.lgd.config.get("Legendary", "install_dir")
|
||||
|
||||
self.install_dir_edit = PathEdit(path=default_path,
|
||||
file_type=QFileDialog.DirectoryOnly,
|
||||
edit_func=self.option_changed,
|
||||
parent=self)
|
||||
self.install_dir_edit = PathEdit(
|
||||
path=default_path,
|
||||
file_type=QFileDialog.DirectoryOnly,
|
||||
edit_func=self.option_changed,
|
||||
parent=self,
|
||||
)
|
||||
self.install_dir_layout.addWidget(self.install_dir_edit)
|
||||
|
||||
if self.update:
|
||||
|
@ -66,10 +70,23 @@ class InstallDialog(QDialog, Ui_InstallDialog):
|
|||
if dl_item.options.app_name in shared.api_results.mac_games:
|
||||
platforms.append("Mac")
|
||||
self.platform_combo_box.addItems(platforms)
|
||||
self.platform_combo_box.currentIndexChanged.connect(lambda: self.option_changed(None))
|
||||
self.platform_combo_box.currentIndexChanged.connect(lambda i: QMessageBox.warning(self, "Warning", self.tr(
|
||||
"You will not be able to run the Game if you choose {}").format(self.platform_combo_box.itemText(i)))
|
||||
if (self.platform_combo_box.currentText() == "Mac" and platform.system() != "Darwin") else None)
|
||||
self.platform_combo_box.currentIndexChanged.connect(
|
||||
lambda: self.option_changed(None)
|
||||
)
|
||||
self.platform_combo_box.currentIndexChanged.connect(
|
||||
lambda i: QMessageBox.warning(
|
||||
self,
|
||||
"Warning",
|
||||
self.tr("You will not be able to run the Game if you choose {}").format(
|
||||
self.platform_combo_box.itemText(i)
|
||||
),
|
||||
)
|
||||
if (
|
||||
self.platform_combo_box.currentText() == "Mac"
|
||||
and platform.system() != "Darwin"
|
||||
)
|
||||
else None
|
||||
)
|
||||
|
||||
if platform.system() == "Darwin" and "Mac" in platforms:
|
||||
self.platform_combo_box.setCurrentIndex(platforms.index("Mac"))
|
||||
|
@ -83,14 +100,16 @@ class InstallDialog(QDialog, Ui_InstallDialog):
|
|||
|
||||
self.force_download_check.stateChanged.connect(self.option_changed)
|
||||
self.ignore_space_check.stateChanged.connect(self.option_changed)
|
||||
self.download_only_check.stateChanged.connect(lambda: self.non_reload_option_changed("download_only"))
|
||||
self.download_only_check.stateChanged.connect(
|
||||
lambda: self.non_reload_option_changed("download_only")
|
||||
)
|
||||
|
||||
self.sdl_list_checks = list()
|
||||
try:
|
||||
for key, info in games[self.app_name].items():
|
||||
cb = QDataCheckBox(info['name'], info['tags'])
|
||||
if key == '__required':
|
||||
self.dl_item.options.sdl_list.extend(info['tags'])
|
||||
cb = QDataCheckBox(info["name"], info["tags"])
|
||||
if key == "__required":
|
||||
self.dl_item.options.sdl_list.extend(info["tags"])
|
||||
cb.setChecked(True)
|
||||
cb.setDisabled(True)
|
||||
self.sdl_list_layout.addWidget(cb)
|
||||
|
@ -120,13 +139,15 @@ class InstallDialog(QDialog, Ui_InstallDialog):
|
|||
self.show()
|
||||
|
||||
def get_options(self):
|
||||
self.dl_item.options.base_path = self.install_dir_edit.text() if not self.update else None
|
||||
self.dl_item.options.base_path = (
|
||||
self.install_dir_edit.text() if not self.update else None
|
||||
)
|
||||
self.dl_item.options.max_workers = self.max_workers_spin.value()
|
||||
self.dl_item.options.force = self.force_download_check.isChecked()
|
||||
self.dl_item.options.ignore_space_req = self.ignore_space_check.isChecked()
|
||||
self.dl_item.options.no_install = self.download_only_check.isChecked()
|
||||
self.dl_item.options.platform = self.platform_combo_box.currentText()
|
||||
self.dl_item.options.sdl_list = ['']
|
||||
self.dl_item.options.sdl_list = [""]
|
||||
for cb in self.sdl_list_checks:
|
||||
if data := cb.isChecked():
|
||||
# noinspection PyTypeChecker
|
||||
|
@ -145,9 +166,13 @@ class InstallDialog(QDialog, Ui_InstallDialog):
|
|||
def verify_clicked(self):
|
||||
message = self.tr("Updating...")
|
||||
self.download_size_info_label.setText(message)
|
||||
self.download_size_info_label.setStyleSheet("font-style: italic; font-weight: normal")
|
||||
self.download_size_info_label.setStyleSheet(
|
||||
"font-style: italic; font-weight: normal"
|
||||
)
|
||||
self.install_size_info_label.setText(message)
|
||||
self.install_size_info_label.setStyleSheet("font-style: italic; font-weight: normal")
|
||||
self.install_size_info_label.setStyleSheet(
|
||||
"font-style: italic; font-weight: normal"
|
||||
)
|
||||
self.cancel_button.setEnabled(False)
|
||||
self.verify_button.setEnabled(False)
|
||||
self.install_button.setEnabled(False)
|
||||
|
@ -180,13 +205,19 @@ class InstallDialog(QDialog, Ui_InstallDialog):
|
|||
install_size = self.dl_item.download.analysis.install_size
|
||||
if download_size:
|
||||
self.download_size_info_label.setText("{}".format(get_size(download_size)))
|
||||
self.download_size_info_label.setStyleSheet("font-style: normal; font-weight: bold")
|
||||
self.download_size_info_label.setStyleSheet(
|
||||
"font-style: normal; font-weight: bold"
|
||||
)
|
||||
self.install_button.setEnabled(not self.options_changed)
|
||||
else:
|
||||
self.install_size_info_label.setText(self.tr("Game already installed"))
|
||||
self.install_size_info_label.setStyleSheet("font-style: italics; font-weight: normal")
|
||||
self.install_size_info_label.setStyleSheet(
|
||||
"font-style: italics; font-weight: normal"
|
||||
)
|
||||
self.install_size_info_label.setText("{}".format(get_size(install_size)))
|
||||
self.install_size_info_label.setStyleSheet("font-style: normal; font-weight: bold")
|
||||
self.install_size_info_label.setStyleSheet(
|
||||
"font-style: normal; font-weight: bold"
|
||||
)
|
||||
self.verify_button.setEnabled(self.options_changed)
|
||||
self.cancel_button.setEnabled(True)
|
||||
if self.silent:
|
||||
|
@ -225,7 +256,6 @@ class InstallInfoWorkerSignals(QObject):
|
|||
|
||||
|
||||
class InstallInfoWorker(QRunnable):
|
||||
|
||||
def __init__(self, core: LegendaryCore, dl_item: InstallQueueItemModel):
|
||||
super(InstallInfoWorker, self).__init__()
|
||||
self.signals = InstallInfoWorkerSignals()
|
||||
|
@ -235,44 +265,47 @@ class InstallInfoWorker(QRunnable):
|
|||
@pyqtSlot()
|
||||
def run(self):
|
||||
try:
|
||||
download = InstallDownloadModel(*self.core.prepare_download(
|
||||
app_name=self.dl_item.options.app_name,
|
||||
base_path=self.dl_item.options.base_path,
|
||||
force=self.dl_item.options.force,
|
||||
no_install=self.dl_item.options.no_install,
|
||||
status_q=self.dl_item.status_q,
|
||||
# max_shm=,
|
||||
max_workers=self.dl_item.options.max_workers,
|
||||
# game_folder=,
|
||||
# disable_patching=,
|
||||
# override_manifest=,
|
||||
# override_old_manifest=,
|
||||
# override_base_url=,
|
||||
platform=self.dl_item.options.platform,
|
||||
# file_prefix_filter=,
|
||||
# file_exclude_filter=,
|
||||
# file_install_tag=,
|
||||
# dl_optimizations=,
|
||||
# dl_timeout=,
|
||||
repair=self.dl_item.options.repair,
|
||||
# repair_use_latest=,
|
||||
ignore_space_req=self.dl_item.options.ignore_space_req,
|
||||
# disable_delta=,
|
||||
# override_delta_manifest=,
|
||||
# reset_sdl=,
|
||||
sdl_prompt=lambda app_name, title: self.dl_item.options.sdl_list
|
||||
))
|
||||
download = InstallDownloadModel(
|
||||
*self.core.prepare_download(
|
||||
app_name=self.dl_item.options.app_name,
|
||||
base_path=self.dl_item.options.base_path,
|
||||
force=self.dl_item.options.force,
|
||||
no_install=self.dl_item.options.no_install,
|
||||
status_q=self.dl_item.status_q,
|
||||
# max_shm=,
|
||||
max_workers=self.dl_item.options.max_workers,
|
||||
# game_folder=,
|
||||
# disable_patching=,
|
||||
# override_manifest=,
|
||||
# override_old_manifest=,
|
||||
# override_base_url=,
|
||||
platform=self.dl_item.options.platform,
|
||||
# file_prefix_filter=,
|
||||
# file_exclude_filter=,
|
||||
# file_install_tag=,
|
||||
# dl_optimizations=,
|
||||
# dl_timeout=,
|
||||
repair=self.dl_item.options.repair,
|
||||
# repair_use_latest=,
|
||||
ignore_space_req=self.dl_item.options.ignore_space_req,
|
||||
# disable_delta=,
|
||||
# override_delta_manifest=,
|
||||
# reset_sdl=,
|
||||
sdl_prompt=lambda app_name, title: self.dl_item.options.sdl_list,
|
||||
)
|
||||
)
|
||||
if 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)
|
||||
)
|
||||
except Exception as e:
|
||||
self.signals.failed.emit(str(e))
|
||||
self.signals.finished.emit()
|
||||
|
||||
|
||||
class QDataCheckBox(QCheckBox):
|
||||
|
||||
def __init__(self, text, data=None, parent=None):
|
||||
super(QDataCheckBox, self).__init__(parent)
|
||||
self.setText(text)
|
||||
|
|
|
@ -75,8 +75,8 @@ class AssetWorker(QRunnable):
|
|||
if not shared.core.egs.user:
|
||||
return []
|
||||
assets = [
|
||||
GameAsset.from_egs_json(a) for a in
|
||||
shared.core.egs.get_game_assets(platform=p)
|
||||
GameAsset.from_egs_json(a)
|
||||
for a in shared.core.egs.get_game_assets(platform=p)
|
||||
]
|
||||
|
||||
return assets
|
||||
|
@ -144,15 +144,24 @@ class LaunchDialog(QDialog, Ui_LaunchDialog):
|
|||
|
||||
# cloud save from another worker, because it is used in cloud_save_utils too
|
||||
cloud_worker = CloudWorker()
|
||||
cloud_worker.signals.result_ready.connect(lambda x: self.handle_api_worker_result(x, "saves"))
|
||||
cloud_worker.signals.result_ready.connect(
|
||||
lambda x: self.handle_api_worker_result(x, "saves")
|
||||
)
|
||||
self.thread_pool.start(cloud_worker)
|
||||
|
||||
else:
|
||||
self.finished = 2
|
||||
if self.core.lgd.assets:
|
||||
self.api_results.game_list, self.api_results.dlcs = self.core.get_game_and_dlc_list(False)
|
||||
self.api_results.bit32_games = list(map(lambda i: i.app_name, self.core.get_game_list(False, "Win32")))
|
||||
self.api_results.mac_games = list(map(lambda i: i.app_name, self.core.get_game_list(False, "Mac")))
|
||||
(
|
||||
self.api_results.game_list,
|
||||
self.api_results.dlcs,
|
||||
) = self.core.get_game_and_dlc_list(False)
|
||||
self.api_results.bit32_games = list(
|
||||
map(lambda i: i.app_name, self.core.get_game_list(False, "Win32"))
|
||||
)
|
||||
self.api_results.mac_games = list(
|
||||
map(lambda i: i.app_name, self.core.get_game_list(False, "Mac"))
|
||||
)
|
||||
else:
|
||||
logger.warning("No assets found. Falling back to empty game lists")
|
||||
self.api_results.game_list, self.api_results.dlcs = [], {}
|
||||
|
@ -165,11 +174,18 @@ class LaunchDialog(QDialog, Ui_LaunchDialog):
|
|||
if result:
|
||||
self.api_results.game_list, self.api_results.dlcs = result
|
||||
else:
|
||||
self.api_results.game_list, self.api_results.dlcs = self.core.get_game_and_dlc_list(False)
|
||||
(
|
||||
self.api_results.game_list,
|
||||
self.api_results.dlcs,
|
||||
) = self.core.get_game_and_dlc_list(False)
|
||||
elif text == "32bit":
|
||||
self.api_results.bit32_games = [i.app_name for i in result[0]] if result else []
|
||||
self.api_results.bit32_games = (
|
||||
[i.app_name for i in result[0]] if result else []
|
||||
)
|
||||
elif text == "mac":
|
||||
self.api_results.mac_games = [i.app_name for i in result[0]] if result else []
|
||||
self.api_results.mac_games = (
|
||||
[i.app_name for i in result[0]] if result else []
|
||||
)
|
||||
|
||||
elif text == "no_assets":
|
||||
self.api_results.no_asset_games = result if result else []
|
||||
|
|
|
@ -48,8 +48,12 @@ class LoginDialog(QDialog, Ui_LoginDialog):
|
|||
self.next_button.setEnabled(False)
|
||||
self.back_button.setEnabled(False)
|
||||
|
||||
self.login_browser_radio.clicked.connect(lambda: self.next_button.setEnabled(True))
|
||||
self.login_import_radio.clicked.connect(lambda: self.next_button.setEnabled(True))
|
||||
self.login_browser_radio.clicked.connect(
|
||||
lambda: self.next_button.setEnabled(True)
|
||||
)
|
||||
self.login_import_radio.clicked.connect(
|
||||
lambda: self.next_button.setEnabled(True)
|
||||
)
|
||||
self.exit_button.clicked.connect(self.close)
|
||||
self.back_button.clicked.connect(self.back_clicked)
|
||||
self.next_button.clicked.connect(self.next_clicked)
|
||||
|
@ -97,4 +101,3 @@ class LoginDialog(QDialog, Ui_LoginDialog):
|
|||
self.next_button.setEnabled(False)
|
||||
self.logged_in = False
|
||||
QMessageBox.warning(self, "Error", str(e))
|
||||
|
||||
|
|
|
@ -25,9 +25,7 @@ class BrowserLogin(QWidget, Ui_BrowserLogin):
|
|||
self.core = core
|
||||
|
||||
self.sid_edit = IndicatorLineEdit(
|
||||
ph_text=self.tr("Insert SID here"),
|
||||
edit_func=self.text_changed,
|
||||
parent=self
|
||||
ph_text=self.tr("Insert SID here"), edit_func=self.text_changed, parent=self
|
||||
)
|
||||
self.sid_layout.addWidget(self.sid_edit)
|
||||
|
||||
|
@ -58,7 +56,9 @@ class BrowserLogin(QWidget, Ui_BrowserLogin):
|
|||
try:
|
||||
token = self.core.auth_sid(sid)
|
||||
if self.core.auth_code(token):
|
||||
logger.info(f"Successfully logged in as {self.core.lgd.userdata['displayName']}")
|
||||
logger.info(
|
||||
f"Successfully logged in as {self.core.lgd.userdata['displayName']}"
|
||||
)
|
||||
self.success.emit()
|
||||
else:
|
||||
self.status_label.setText(self.tr("Login failed."))
|
||||
|
|
|
@ -17,7 +17,9 @@ class ImportLogin(QWidget, Ui_ImportLogin):
|
|||
if os.name == "nt":
|
||||
localappdata = os.path.expandvars("%LOCALAPPDATA%")
|
||||
else:
|
||||
localappdata = os.path.join("drive_c/users", getuser(), "Local Settings/Application Data")
|
||||
localappdata = os.path.join(
|
||||
"drive_c/users", getuser(), "Local Settings/Application Data"
|
||||
)
|
||||
appdata_path = os.path.join(localappdata, "EpicGamesLauncher/Saved/Config/Windows")
|
||||
found = False
|
||||
|
||||
|
@ -27,7 +29,9 @@ class ImportLogin(QWidget, Ui_ImportLogin):
|
|||
|
||||
self.core = core
|
||||
|
||||
self.text_egl_found = self.tr("Found EGL Program Data. Click 'Next' to import them.")
|
||||
self.text_egl_found = self.tr(
|
||||
"Found EGL Program Data. Click 'Next' to import them."
|
||||
)
|
||||
self.text_egl_notfound = self.tr("Could not find EGL Program Data. ")
|
||||
|
||||
if os.name == "nt":
|
||||
|
@ -39,14 +43,19 @@ class ImportLogin(QWidget, Ui_ImportLogin):
|
|||
self.status_label.setText(self.text_egl_found)
|
||||
self.found = True
|
||||
else:
|
||||
self.info_label.setText(self.tr(
|
||||
"Please select the Wine prefix"
|
||||
" where Epic Games Launcher is installed. ") + self.info_label.text()
|
||||
)
|
||||
self.info_label.setText(
|
||||
self.tr(
|
||||
"Please select the Wine prefix"
|
||||
" where Epic Games Launcher is installed. "
|
||||
)
|
||||
+ self.info_label.text()
|
||||
)
|
||||
prefixes = self.get_wine_prefixes()
|
||||
if len(prefixes):
|
||||
self.prefix_combo.addItems(prefixes)
|
||||
self.status_label.setText(self.tr("Select the Wine prefix you want to import."))
|
||||
self.status_label.setText(
|
||||
self.tr("Select the Wine prefix you want to import.")
|
||||
)
|
||||
else:
|
||||
self.status_label.setText(self.text_egl_notfound)
|
||||
|
||||
|
@ -65,7 +74,9 @@ class ImportLogin(QWidget, Ui_ImportLogin):
|
|||
return prefixes
|
||||
|
||||
def prefix_path(self):
|
||||
prefix_dialog = QFileDialog(self, self.tr("Choose path"), os.path.expanduser("~/"))
|
||||
prefix_dialog = QFileDialog(
|
||||
self, self.tr("Choose path"), os.path.expanduser("~/")
|
||||
)
|
||||
prefix_dialog.setFileMode(QFileDialog.DirectoryOnly)
|
||||
if prefix_dialog.exec_():
|
||||
names = prefix_dialog.selectedFiles()
|
||||
|
@ -75,14 +86,18 @@ class ImportLogin(QWidget, Ui_ImportLogin):
|
|||
if os.name == "nt":
|
||||
return self.found
|
||||
else:
|
||||
return os.path.exists(os.path.join(self.prefix_combo.currentText(), self.appdata_path))
|
||||
return os.path.exists(
|
||||
os.path.join(self.prefix_combo.currentText(), self.appdata_path)
|
||||
)
|
||||
|
||||
def do_login(self):
|
||||
self.status_label.setText(self.tr("Loading..."))
|
||||
if os.name == "nt":
|
||||
pass
|
||||
else:
|
||||
self.core.egl.appdata_path = os.path.join(self.prefix_combo.currentText(), self.appdata_path)
|
||||
self.core.egl.appdata_path = os.path.join(
|
||||
self.prefix_combo.currentText(), self.appdata_path
|
||||
)
|
||||
try:
|
||||
if self.core.auth_import():
|
||||
logger.info(f"Logged in as {self.core.lgd.userdata['displayName']}")
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QPushButton, QVBoxLayout, QLabel, QDialog, QFileDialog
|
||||
from PyQt5.QtWidgets import (
|
||||
QHBoxLayout,
|
||||
QPushButton,
|
||||
QVBoxLayout,
|
||||
QLabel,
|
||||
QDialog,
|
||||
QFileDialog,
|
||||
)
|
||||
|
||||
from rare.utils.extra_widgets import PathEdit
|
||||
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QDialog, QLabel, QVBoxLayout, QCheckBox, QFormLayout, QHBoxLayout, QPushButton
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog,
|
||||
QLabel,
|
||||
QVBoxLayout,
|
||||
QCheckBox,
|
||||
QFormLayout,
|
||||
QHBoxLayout,
|
||||
QPushButton,
|
||||
)
|
||||
from qtawesome import icon
|
||||
|
||||
from legendary.models.game import Game
|
||||
|
@ -12,7 +20,9 @@ class UninstallDialog(QDialog):
|
|||
self.info = 0
|
||||
self.setAttribute(Qt.WA_DeleteOnClose, True)
|
||||
self.layout = QVBoxLayout()
|
||||
self.info_text = QLabel(self.tr("Do you really want to uninstall {}").format(game.app_title))
|
||||
self.info_text = QLabel(
|
||||
self.tr("Do you really want to uninstall {}").format(game.app_title)
|
||||
)
|
||||
self.layout.addWidget(self.info_text)
|
||||
self.keep_files = QCheckBox(self.tr("Keep Files"))
|
||||
self.form = QFormLayout()
|
||||
|
@ -21,7 +31,9 @@ class UninstallDialog(QDialog):
|
|||
self.layout.addLayout(self.form)
|
||||
|
||||
self.button_layout = QHBoxLayout()
|
||||
self.ok_button = QPushButton(icon("ei.remove-circle", color="red"), self.tr("Uninstall"))
|
||||
self.ok_button = QPushButton(
|
||||
icon("ei.remove-circle", color="red"), self.tr("Uninstall")
|
||||
)
|
||||
self.ok_button.clicked.connect(self.ok)
|
||||
|
||||
self.cancel_button = QPushButton(self.tr("Cancel"))
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
from PyQt5.QtGui import QTextCursor, QFont
|
||||
from PyQt5.QtWidgets import QPlainTextEdit, QWidget, QPushButton, QFileDialog, QVBoxLayout
|
||||
from PyQt5.QtWidgets import (
|
||||
QPlainTextEdit,
|
||||
QWidget,
|
||||
QPushButton,
|
||||
QFileDialog,
|
||||
QVBoxLayout,
|
||||
)
|
||||
|
||||
|
||||
class ConsoleWindow(QWidget):
|
||||
|
@ -22,7 +28,9 @@ class ConsoleWindow(QWidget):
|
|||
self.setLayout(self.layout)
|
||||
|
||||
def save(self):
|
||||
file, ok = QFileDialog.getSaveFileName(self, "Save output", "", "Log Files (*.log);;All Files (*)")
|
||||
file, ok = QFileDialog.getSaveFileName(
|
||||
self, "Save output", "", "Log Files (*.log);;All Files (*)"
|
||||
)
|
||||
if ok:
|
||||
if "." not in file:
|
||||
file += ".log"
|
||||
|
@ -39,7 +47,6 @@ class ConsoleWindow(QWidget):
|
|||
|
||||
|
||||
class Console(QPlainTextEdit):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setReadOnly(True)
|
||||
|
@ -51,12 +58,13 @@ class Console(QPlainTextEdit):
|
|||
self.scroll_to_last_line()
|
||||
|
||||
def error(self, text):
|
||||
self._cursor_output.insertHtml(f"<font color=\"Red\">{text}</font>")
|
||||
self._cursor_output.insertHtml(f'<font color="Red">{text}</font>')
|
||||
self.scroll_to_last_line()
|
||||
|
||||
def scroll_to_last_line(self):
|
||||
cursor = self.textCursor()
|
||||
cursor.movePosition(QTextCursor.End)
|
||||
cursor.movePosition(QTextCursor.Up if cursor.atBlockStart() else
|
||||
QTextCursor.StartOfLine)
|
||||
cursor.movePosition(
|
||||
QTextCursor.Up if cursor.atBlockStart() else QTextCursor.StartOfLine
|
||||
)
|
||||
self.setTextCursor(cursor)
|
||||
|
|
|
@ -13,7 +13,6 @@ logger = getLogger("Window")
|
|||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
|
||||
def __init__(self):
|
||||
super(MainWindow, self).__init__()
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
|
@ -54,10 +53,14 @@ class MainWindow(QMainWindow):
|
|||
decor_width = margins.left() + margins.right()
|
||||
decor_height = margins.top() + margins.bottom()
|
||||
window_size = QSize(self.width(), self.height()).boundedTo(
|
||||
screen_rect.size() - QSize(decor_width, decor_height))
|
||||
screen_rect.size() - QSize(decor_width, decor_height)
|
||||
)
|
||||
|
||||
self.resize(window_size)
|
||||
self.move(screen_rect.center() - self.rect().adjusted(0, 0, decor_width, decor_height).center())
|
||||
self.move(
|
||||
screen_rect.center()
|
||||
- self.rect().adjusted(0, 0, decor_width, decor_height).center()
|
||||
)
|
||||
|
||||
def timer_finished(self):
|
||||
file_path = os.path.join(data_dir, "lockfile")
|
||||
|
@ -67,8 +70,12 @@ class MainWindow(QMainWindow):
|
|||
file.close()
|
||||
if action.startswith("launch"):
|
||||
game = action.replace("launch ", "").replace("\n", "")
|
||||
if game in [i.app_name for i in self.tab_widget.games_tab.game_list] and self.core.is_installed(game):
|
||||
self.tab_widget.games_tab.game_utils.prepare_launch(game, offline=shared.args.offline)
|
||||
if game in [
|
||||
i.app_name for i in self.tab_widget.games_tab.game_list
|
||||
] and self.core.is_installed(game):
|
||||
self.tab_widget.games_tab.game_utils.prepare_launch(
|
||||
game, offline=shared.args.offline
|
||||
)
|
||||
else:
|
||||
logger.info(f"Could not find {game} in Games")
|
||||
elif action.startswith("start"):
|
||||
|
|
|
@ -29,8 +29,15 @@ class TabWidget(QTabWidget):
|
|||
if not shared.args.offline:
|
||||
# updates = self.games_tab.default_widget.game_list.updates
|
||||
self.downloadTab = DownloadsTab(self.games_tab.updates)
|
||||
self.addTab(self.downloadTab, "Downloads" + (
|
||||
" (" + str(len(self.games_tab.updates)) + ")" if len(self.games_tab.updates) != 0 else ""))
|
||||
self.addTab(
|
||||
self.downloadTab,
|
||||
"Downloads"
|
||||
+ (
|
||||
" (" + str(len(self.games_tab.updates)) + ")"
|
||||
if len(self.games_tab.updates) != 0
|
||||
else ""
|
||||
),
|
||||
)
|
||||
self.store = Shop(self.core)
|
||||
self.addTab(self.store, self.tr("Store (Beta)"))
|
||||
|
||||
|
@ -49,14 +56,18 @@ class TabWidget(QTabWidget):
|
|||
self.mini_widget = MiniWidget()
|
||||
account_action = QWidgetAction(self)
|
||||
account_action.setDefaultWidget(self.mini_widget)
|
||||
account_button = TabButtonWidget('mdi.account-circle', 'Account')
|
||||
account_button = TabButtonWidget("mdi.account-circle", "Account")
|
||||
account_button.setMenu(QMenu())
|
||||
account_button.menu().addAction(account_action)
|
||||
self.tabBar().setTabButton(disabled_tab + 1, self.tabBar().RightSide, account_button)
|
||||
self.tabBar().setTabButton(
|
||||
disabled_tab + 1, self.tabBar().RightSide, account_button
|
||||
)
|
||||
|
||||
self.addTab(self.settings, icon("fa.gear"), "")
|
||||
|
||||
self.settings.about.update_available_ready.connect(lambda: self.tabBar().setTabText(5, "(!)"))
|
||||
self.settings.about.update_available_ready.connect(
|
||||
lambda: self.tabBar().setTabText(5, "(!)")
|
||||
)
|
||||
# Signals
|
||||
# set current index
|
||||
self.signals.set_main_tab_index.connect(self.setCurrentIndex)
|
||||
|
@ -75,8 +86,12 @@ class TabWidget(QTabWidget):
|
|||
QShortcut("Alt+4", self).activated.connect(lambda: self.setCurrentIndex(5))
|
||||
|
||||
def update_dl_tab_text(self):
|
||||
num_downloads = len(set([i.options.app_name for i in self.downloadTab.dl_queue] + [i for i in
|
||||
self.downloadTab.update_widgets.keys()]))
|
||||
num_downloads = len(
|
||||
set(
|
||||
[i.options.app_name for i in self.downloadTab.dl_queue]
|
||||
+ [i for i in self.downloadTab.update_widgets.keys()]
|
||||
)
|
||||
)
|
||||
|
||||
if num_downloads != 0:
|
||||
self.setTabText(1, f"Downloads ({num_downloads})")
|
||||
|
|
|
@ -20,7 +20,10 @@ class MiniWidget(QWidget):
|
|||
|
||||
self.open_browser = QPushButton(self.tr("Account settings"))
|
||||
self.open_browser.clicked.connect(
|
||||
lambda: webbrowser.open("https://www.epicgames.com/account/personal?productName=epicgames"))
|
||||
lambda: webbrowser.open(
|
||||
"https://www.epicgames.com/account/personal?productName=epicgames"
|
||||
)
|
||||
)
|
||||
self.layout.addWidget(self.open_browser)
|
||||
|
||||
self.logout_button = QPushButton(self.tr("Logout"))
|
||||
|
@ -29,9 +32,13 @@ class MiniWidget(QWidget):
|
|||
self.setLayout(self.layout)
|
||||
|
||||
def logout(self):
|
||||
reply = QMessageBox.question(self.parent().parent(), 'Message',
|
||||
self.tr("Do you really want to logout"), QMessageBox.Yes |
|
||||
QMessageBox.No, QMessageBox.No)
|
||||
reply = QMessageBox.question(
|
||||
self.parent().parent(),
|
||||
"Message",
|
||||
self.tr("Do you really want to logout"),
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No,
|
||||
)
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
self.core.lgd.invalidate_userdata()
|
||||
|
|
|
@ -3,8 +3,14 @@ from logging import getLogger
|
|||
from typing import List, Dict
|
||||
|
||||
from PyQt5.QtCore import QThread, pyqtSignal, QSettings
|
||||
from PyQt5.QtWidgets import QWidget, QMessageBox, QVBoxLayout, QLabel, QPushButton, \
|
||||
QGroupBox
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget,
|
||||
QMessageBox,
|
||||
QVBoxLayout,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QGroupBox,
|
||||
)
|
||||
|
||||
from legendary.core import LegendaryCore
|
||||
from legendary.models.downloading import UIUpdate
|
||||
|
@ -59,7 +65,9 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
|
|||
self.signals.game_uninstalled.connect(self.queue_item_removed)
|
||||
self.signals.game_uninstalled.connect(self.remove_update)
|
||||
|
||||
self.signals.add_download.connect(lambda app_name: self.add_update(self.core.get_installed_game(app_name)))
|
||||
self.signals.add_download.connect(
|
||||
lambda app_name: self.add_update(self.core.get_installed_game(app_name))
|
||||
)
|
||||
shared.signals.game_uninstalled.connect(self.game_uninstalled)
|
||||
|
||||
self.reset_infos()
|
||||
|
@ -75,7 +83,9 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
|
|||
self.update_widgets[igame.app_name] = widget
|
||||
widget.update_signal.connect(self.get_install_options)
|
||||
if QSettings().value("auto_update", False, bool):
|
||||
self.get_install_options(InstallOptionsModel(app_name=igame.app_name, update=True, silent=True))
|
||||
self.get_install_options(
|
||||
InstallOptionsModel(app_name=igame.app_name, update=True, silent=True)
|
||||
)
|
||||
widget.update_button.setDisabled(True)
|
||||
self.update_text.setVisible(False)
|
||||
|
||||
|
@ -148,7 +158,12 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
|
|||
|
||||
if game.app_name in self.update_widgets.keys():
|
||||
igame = self.core.get_installed_game(game.app_name)
|
||||
if self.core.get_asset(game.app_name, igame.platform, False).build_version == igame.version:
|
||||
if (
|
||||
self.core.get_asset(
|
||||
game.app_name, igame.platform, False
|
||||
).build_version
|
||||
== igame.version
|
||||
):
|
||||
self.remove_update(game.app_name)
|
||||
|
||||
self.signals.send_notification.emit(game.app_title)
|
||||
|
@ -188,12 +203,20 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
|
|||
self.analysis = None
|
||||
|
||||
def statistics(self, ui_update: UIUpdate):
|
||||
self.progress_bar.setValue(100 * ui_update.total_downloaded // self.analysis.dl_size)
|
||||
self.progress_bar.setValue(
|
||||
100 * ui_update.total_downloaded // self.analysis.dl_size
|
||||
)
|
||||
self.dl_speed.setText(f"{get_size(ui_update.download_speed)}/s")
|
||||
self.cache_used.setText(f"{get_size(ui_update.cache_usage) if ui_update.cache_usage > 1023 else '0KB'}")
|
||||
self.downloaded.setText(f"{get_size(ui_update.total_downloaded)} / {get_size(self.analysis.dl_size)}")
|
||||
self.cache_used.setText(
|
||||
f"{get_size(ui_update.cache_usage) if ui_update.cache_usage > 1023 else '0KB'}"
|
||||
)
|
||||
self.downloaded.setText(
|
||||
f"{get_size(ui_update.total_downloaded)} / {get_size(self.analysis.dl_size)}"
|
||||
)
|
||||
self.time_left.setText(self.get_time(ui_update.estimated_time_left))
|
||||
self.signals.dl_progress.emit(100 * ui_update.total_downloaded // self.analysis.dl_size)
|
||||
self.signals.dl_progress.emit(
|
||||
100 * ui_update.total_downloaded // self.analysis.dl_size
|
||||
)
|
||||
|
||||
def get_time(self, seconds: int) -> str:
|
||||
return str(datetime.timedelta(seconds=seconds))
|
||||
|
@ -209,14 +232,24 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
|
|||
|
||||
def get_install_options(self, options: InstallOptionsModel):
|
||||
|
||||
install_dialog = InstallDialog(InstallQueueItemModel(options=options),
|
||||
update=options.update, silent=options.silent, parent=self)
|
||||
install_dialog = InstallDialog(
|
||||
InstallQueueItemModel(options=options),
|
||||
update=options.update,
|
||||
silent=options.silent,
|
||||
parent=self,
|
||||
)
|
||||
install_dialog.result_ready.connect(self.on_install_dialog_closed)
|
||||
install_dialog.execute()
|
||||
|
||||
def start_download(self, download_item: InstallQueueItemModel):
|
||||
downloads = len(self.downloadTab.dl_queue) + len(self.downloadTab.update_widgets.keys()) + 1
|
||||
self.setTabText(1, "Downloads" + ((" (" + str(downloads) + ")") if downloads != 0 else ""))
|
||||
downloads = (
|
||||
len(self.downloadTab.dl_queue)
|
||||
+ len(self.downloadTab.update_widgets.keys())
|
||||
+ 1
|
||||
)
|
||||
self.setTabText(
|
||||
1, "Downloads" + ((" (" + str(downloads) + ")") if downloads != 0 else "")
|
||||
)
|
||||
self.setCurrentIndex(1)
|
||||
self.downloadTab.install_game(download_item)
|
||||
self.games_tab.start_download(download_item.options.app_name)
|
||||
|
@ -244,13 +277,22 @@ class UpdateWidget(QWidget):
|
|||
self.update_with_settings.clicked.connect(lambda: self.update_game(False))
|
||||
self.layout.addWidget(self.update_button)
|
||||
self.layout.addWidget(self.update_with_settings)
|
||||
self.layout.addWidget(QLabel(
|
||||
self.tr("Version: ") + self.game.version + " -> " +
|
||||
self.core.get_asset(self.game.app_name, self.game.platform, True).build_version))
|
||||
self.layout.addWidget(
|
||||
QLabel(
|
||||
self.tr("Version: ")
|
||||
+ self.game.version
|
||||
+ " -> "
|
||||
+ self.core.get_asset(
|
||||
self.game.app_name, self.game.platform, True
|
||||
).build_version
|
||||
)
|
||||
)
|
||||
|
||||
self.setLayout(self.layout)
|
||||
|
||||
def update_game(self, auto: bool):
|
||||
self.update_button.setDisabled(True)
|
||||
self.update_with_settings.setDisabled(True)
|
||||
self.update_signal.emit(InstallOptionsModel(app_name=self.game.app_name, silent=auto)) # True if settings
|
||||
self.update_signal.emit(
|
||||
InstallOptionsModel(app_name=self.game.app_name, silent=auto)
|
||||
) # True if settings
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
from logging import getLogger
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QLabel, QHBoxLayout, QPushButton, QWidget
|
||||
from PyQt5.QtWidgets import (
|
||||
QGroupBox,
|
||||
QVBoxLayout,
|
||||
QLabel,
|
||||
QHBoxLayout,
|
||||
QPushButton,
|
||||
QWidget,
|
||||
)
|
||||
from qtawesome import icon
|
||||
|
||||
from rare.utils.models import InstallQueueItemModel
|
||||
|
@ -28,7 +35,9 @@ class DlWidget(QWidget):
|
|||
self.left_layout.addWidget(self.move_up_button)
|
||||
|
||||
self.move_down_buttton = QPushButton(icon("fa.arrow-down"), "")
|
||||
self.move_down_buttton.clicked.connect(lambda: self.move_down.emit(self.app_name))
|
||||
self.move_down_buttton.clicked.connect(
|
||||
lambda: self.move_down.emit(self.app_name)
|
||||
)
|
||||
self.left_layout.addWidget(self.move_down_buttton)
|
||||
self.move_down_buttton.setFixedWidth(20)
|
||||
|
||||
|
@ -43,8 +52,18 @@ class DlWidget(QWidget):
|
|||
|
||||
self.size = QHBoxLayout()
|
||||
|
||||
self.size.addWidget(QLabel(self.tr("Download size: {} GB").format(round(dl_size / 1024 ** 3, 2))))
|
||||
self.size.addWidget(QLabel(self.tr("Install size: {} GB").format(round(install_size / 1024 ** 3, 2))))
|
||||
self.size.addWidget(
|
||||
QLabel(
|
||||
self.tr("Download size: {} GB").format(round(dl_size / 1024 ** 3, 2))
|
||||
)
|
||||
)
|
||||
self.size.addWidget(
|
||||
QLabel(
|
||||
self.tr("Install size: {} GB").format(
|
||||
round(install_size / 1024 ** 3, 2)
|
||||
)
|
||||
)
|
||||
)
|
||||
self.right_layout.addLayout(self.size)
|
||||
|
||||
self.delete = QPushButton(self.tr("Remove Download"))
|
||||
|
@ -69,7 +88,9 @@ class DlQueueWidget(QGroupBox):
|
|||
self.layout().addWidget(self.text)
|
||||
|
||||
def update_queue(self, dl_queue: list):
|
||||
logger.debug("Update Queue " + ", ".join(i.download.game.app_title for i in dl_queue))
|
||||
logger.debug(
|
||||
"Update Queue " + ", ".join(i.download.game.app_title for i in dl_queue)
|
||||
)
|
||||
self.dl_queue = dl_queue
|
||||
|
||||
for item in (self.layout().itemAt(i) for i in range(self.layout().count())):
|
||||
|
|
|
@ -59,12 +59,23 @@ class DownloadThread(QThread):
|
|||
for t in self.dlm.threads:
|
||||
t.join(timeout=5.0)
|
||||
if t.is_alive():
|
||||
logger.warning(f'Thread did not terminate! {repr(t)}')
|
||||
logger.warning(f"Thread did not terminate! {repr(t)}")
|
||||
|
||||
# clean up all the queues, otherwise this process won't terminate properly
|
||||
for name, q in zip(('Download jobs', 'Writer jobs', 'Download results', 'Writer results'),
|
||||
(self.dlm.dl_worker_queue, self.dlm.writer_queue, self.dlm.dl_result_q,
|
||||
self.dlm.writer_result_q)):
|
||||
for name, q in zip(
|
||||
(
|
||||
"Download jobs",
|
||||
"Writer jobs",
|
||||
"Download results",
|
||||
"Writer results",
|
||||
),
|
||||
(
|
||||
self.dlm.dl_worker_queue,
|
||||
self.dlm.writer_queue,
|
||||
self.dlm.dl_result_q,
|
||||
self.dlm.writer_result_q,
|
||||
),
|
||||
):
|
||||
logger.debug(f'Cleaning up queue "{name}"')
|
||||
try:
|
||||
while True:
|
||||
|
@ -73,11 +84,11 @@ class DownloadThread(QThread):
|
|||
q.close()
|
||||
q.join_thread()
|
||||
except AttributeError:
|
||||
logger.warning(f'Queue {name} did not close')
|
||||
logger.warning(f"Queue {name} did not close")
|
||||
|
||||
if self.dlm.writer_queue:
|
||||
# cancel installation
|
||||
self.dlm.writer_queue.put_nowait(WriterTask('', kill=True))
|
||||
self.dlm.writer_queue.put_nowait(WriterTask("", kill=True))
|
||||
|
||||
# forcibly kill DL workers that are not actually dead yet
|
||||
for child in self.dlm.children:
|
||||
|
@ -95,9 +106,16 @@ class DownloadThread(QThread):
|
|||
# force kill any threads that are somehow still alive
|
||||
for proc in psutil.process_iter():
|
||||
# check whether the process name matches
|
||||
if sys.platform in ['linux', 'darwin'] and proc.name() == 'DownloadThread':
|
||||
if (
|
||||
sys.platform in ["linux", "darwin"]
|
||||
and proc.name() == "DownloadThread"
|
||||
):
|
||||
proc.kill()
|
||||
elif sys.platform == 'win32' and proc.name() == 'python.exe' and proc.create_time() >= start_time:
|
||||
elif (
|
||||
sys.platform == "win32"
|
||||
and proc.name() == "python.exe"
|
||||
and proc.create_time() >= start_time
|
||||
):
|
||||
proc.kill()
|
||||
|
||||
logger.info("Download stopped. It can be continued later.")
|
||||
|
@ -111,7 +129,9 @@ class DownloadThread(QThread):
|
|||
self.dlm.join()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Installation failed after {time.time() - start_time:.02f} seconds: {e}")
|
||||
logger.error(
|
||||
f"Installation failed after {time.time() - start_time:.02f} seconds: {e}"
|
||||
)
|
||||
self.status.emit("error " + str(e))
|
||||
return
|
||||
|
||||
|
@ -130,48 +150,65 @@ class DownloadThread(QThread):
|
|||
|
||||
dlcs = self.core.get_dlc_for_game(self.igame.app_name)
|
||||
if dlcs:
|
||||
print('The following DLCs are available for this game:')
|
||||
print("The following DLCs are available for this game:")
|
||||
for dlc in dlcs:
|
||||
print(f' - {dlc.app_title} (App name: {dlc.app_name}, version: {dlc.app_version})')
|
||||
print('Manually installing DLCs works the same; just use the DLC app name instead.')
|
||||
print(
|
||||
f" - {dlc.app_title} (App name: {dlc.app_name}, version: {dlc.app_version})"
|
||||
)
|
||||
print(
|
||||
"Manually installing DLCs works the same; just use the DLC app name instead."
|
||||
)
|
||||
|
||||
# install_dlcs = QMessageBox.question(self, "", "Do you want to install the prequisites", QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes
|
||||
# TODO
|
||||
if game.supports_cloud_saves and not game.is_dlc:
|
||||
logger.info('This game supports cloud saves, syncing is handled by the "sync-saves" command.')
|
||||
logger.info(f'To download saves for this game run "legendary sync-saves {game.app_name}"')
|
||||
logger.info(
|
||||
'This game supports cloud saves, syncing is handled by the "sync-saves" command.'
|
||||
)
|
||||
logger.info(
|
||||
f'To download saves for this game run "legendary sync-saves {game.app_name}"'
|
||||
)
|
||||
old_igame = self.core.get_installed_game(game.app_name)
|
||||
if old_igame and self.repair and os.path.exists(self.repair_file):
|
||||
if old_igame.needs_verification:
|
||||
old_igame.needs_verification = False
|
||||
self.core.install_game(old_igame)
|
||||
|
||||
logger.debug('Removing repair file.')
|
||||
logger.debug("Removing repair file.")
|
||||
os.remove(self.repair_file)
|
||||
if old_igame and old_igame.install_tags != self.igame.install_tags:
|
||||
old_igame.install_tags = self.igame.install_tags
|
||||
self.logger.info('Deleting now untagged files.')
|
||||
self.logger.info("Deleting now untagged files.")
|
||||
self.core.uninstall_tag(old_igame)
|
||||
self.core.install_game(old_igame)
|
||||
|
||||
self.status.emit("finish")
|
||||
|
||||
def _handle_postinstall(self, postinstall, igame):
|
||||
print('This game lists the following prequisites to be installed:')
|
||||
print(f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}')
|
||||
print("This game lists the following prequisites to be installed:")
|
||||
print(
|
||||
f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}'
|
||||
)
|
||||
if platform.system() == "Windows":
|
||||
if QMessageBox.question(self, "", "Do you want to install the prequisites",
|
||||
QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
|
||||
if (
|
||||
QMessageBox.question(
|
||||
self,
|
||||
"",
|
||||
"Do you want to install the prequisites",
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
)
|
||||
== QMessageBox.Yes
|
||||
):
|
||||
self.core.prereq_installed(igame.app_name)
|
||||
req_path, req_exec = os.path.split(postinstall['path'])
|
||||
req_path, req_exec = os.path.split(postinstall["path"])
|
||||
work_dir = os.path.join(igame.install_path, req_path)
|
||||
fullpath = os.path.join(work_dir, req_exec)
|
||||
subprocess.call([fullpath, postinstall['args']], cwd=work_dir)
|
||||
subprocess.call([fullpath, postinstall["args"]], cwd=work_dir)
|
||||
else:
|
||||
self.core.prereq_installed(self.igame.app_name)
|
||||
|
||||
else:
|
||||
logger.info('Automatic installation not available on Linux.')
|
||||
logger.info("Automatic installation not available on Linux.")
|
||||
|
||||
def kill(self):
|
||||
self._kill = True
|
||||
|
|
|
@ -58,7 +58,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
|||
self.game_info_tabs.back_clicked.connect(lambda: self.setCurrentIndex(0))
|
||||
self.addWidget(self.game_info_tabs)
|
||||
|
||||
self.game_info_tabs.info.verification_finished.connect(self.verification_finished)
|
||||
self.game_info_tabs.info.verification_finished.connect(
|
||||
self.verification_finished
|
||||
)
|
||||
self.game_info_tabs.info.uninstalled.connect(lambda x: self.setCurrentIndex(0))
|
||||
|
||||
self.import_sync_tabs = ImportSyncTabs(self)
|
||||
|
@ -102,7 +104,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
|||
self.signals.dl_progress.connect(self.installing_widget.set_status)
|
||||
self.signals.installation_started.connect(self.installation_started)
|
||||
self.signals.update_gamelist.connect(self.update_list)
|
||||
self.signals.installation_finished.connect(lambda x: self.installing_widget.setVisible(False))
|
||||
self.signals.installation_finished.connect(
|
||||
lambda x: self.installing_widget.setVisible(False)
|
||||
)
|
||||
self.signals.game_uninstalled.connect(lambda name: self.update_list([name]))
|
||||
|
||||
self.game_utils.update_list.connect(self.update_list)
|
||||
|
@ -174,9 +178,11 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
|||
self.list_view.layout().addWidget(list_widget)
|
||||
|
||||
def update_count_games_label(self):
|
||||
self.count_games_label.setText(self.tr("Installed Games: {} Available Games: {}").format(
|
||||
len(self.core.get_installed_list()),
|
||||
len(self.game_list)))
|
||||
self.count_games_label.setText(
|
||||
self.tr("Installed Games: {} Available Games: {}").format(
|
||||
len(self.core.get_installed_list()), len(self.game_list)
|
||||
)
|
||||
)
|
||||
|
||||
def add_installed_widget(self, app_name):
|
||||
pixmap = get_pixmap(app_name)
|
||||
|
@ -249,7 +255,10 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
|||
visible = True
|
||||
else:
|
||||
visible = True
|
||||
if search_text.lower() not in w.game.app_name.lower() and search_text.lower() not in w.game.app_title.lower():
|
||||
if (
|
||||
search_text.lower() not in w.game.app_name.lower()
|
||||
and search_text.lower() not in w.game.app_title.lower()
|
||||
):
|
||||
visible = False
|
||||
w.setVisible(visible)
|
||||
|
||||
|
@ -261,16 +270,24 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
|||
if widgets := self.widgets.get(app_name):
|
||||
|
||||
# from update
|
||||
if self.core.is_installed(widgets[0].game.app_name) and isinstance(widgets[0], BaseInstalledWidget):
|
||||
if self.core.is_installed(widgets[0].game.app_name) and isinstance(
|
||||
widgets[0], BaseInstalledWidget
|
||||
):
|
||||
logger.debug("Update Gamelist: Updated: " + app_name)
|
||||
igame = self.core.get_installed_game(app_name)
|
||||
for w in widgets:
|
||||
w.igame = igame
|
||||
w.update_available = self.core.get_asset(w.game.app_name, w.igame.platform,
|
||||
True).build_version != igame.version
|
||||
w.update_available = (
|
||||
self.core.get_asset(
|
||||
w.game.app_name, w.igame.platform, True
|
||||
).build_version
|
||||
!= igame.version
|
||||
)
|
||||
widgets[0].leaveEvent(None)
|
||||
# new installed
|
||||
elif self.core.is_installed(app_name) and isinstance(widgets[0], BaseUninstalledWidget):
|
||||
elif self.core.is_installed(app_name) and isinstance(
|
||||
widgets[0], BaseUninstalledWidget
|
||||
):
|
||||
logger.debug("Update Gamelist: New installed " + app_name)
|
||||
self.widgets[app_name][0].deleteLater()
|
||||
self.widgets[app_name][1].deleteLater()
|
||||
|
@ -280,8 +297,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
|||
update_list = True
|
||||
|
||||
# uninstalled
|
||||
elif not self.core.is_installed(widgets[0].game.app_name) and isinstance(widgets[0],
|
||||
BaseInstalledWidget):
|
||||
elif not self.core.is_installed(
|
||||
widgets[0].game.app_name
|
||||
) and isinstance(widgets[0], BaseInstalledWidget):
|
||||
logger.debug("Update list: Uninstalled: " + app_name)
|
||||
self.widgets[app_name][0].deleteLater()
|
||||
self.widgets[app_name][1].deleteLater()
|
||||
|
@ -305,8 +323,13 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
|||
if not game.app_name in installed_names:
|
||||
uninstalled_names.append(game.app_name)
|
||||
|
||||
new_installed_games = list(set(installed_names) - set([i.app_name for i in self.installed]))
|
||||
new_uninstalled_games = list(set(uninstalled_names) - set([i.app_name for i in self.uninstalled_games]))
|
||||
new_installed_games = list(
|
||||
set(installed_names) - set([i.app_name for i in self.installed])
|
||||
)
|
||||
new_uninstalled_games = list(
|
||||
set(uninstalled_names)
|
||||
- set([i.app_name for i in self.uninstalled_games])
|
||||
)
|
||||
|
||||
if (not new_uninstalled_games) and (not new_installed_games):
|
||||
return
|
||||
|
@ -332,7 +355,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
|||
game = self.core.get_game(name, False)
|
||||
self.add_uninstalled_widget(game)
|
||||
|
||||
for igame in sorted(self.core.get_installed_list(), key=lambda x: x.title):
|
||||
for igame in sorted(
|
||||
self.core.get_installed_list(), key=lambda x: x.title
|
||||
):
|
||||
i_widget, list_widget = self.widgets[igame.app_name]
|
||||
|
||||
self.icon_view.layout().addWidget(i_widget)
|
||||
|
@ -348,7 +373,10 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
|||
self.uninstalled_games = []
|
||||
games, self.dlcs = self.core.get_game_and_dlc_list()
|
||||
for game in sorted(games, key=lambda x: x.app_title):
|
||||
if not self.core.is_installed(game.app_name) and game.app_name not in self.no_asset_names:
|
||||
if (
|
||||
not self.core.is_installed(game.app_name)
|
||||
and game.app_name not in self.no_asset_names
|
||||
):
|
||||
i_widget, list_widget = self.widgets[game.app_name]
|
||||
self.icon_view.layout().addWidget(i_widget)
|
||||
self.list_view.layout().addWidget(list_widget)
|
||||
|
@ -391,8 +419,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
|
|||
self.list_view.setParent(None)
|
||||
|
||||
# insert widget in layout
|
||||
self.scroll_widget.layout().insertWidget(1,
|
||||
self.icon_view if self.head_bar.view.isChecked() else self.list_view)
|
||||
self.scroll_widget.layout().insertWidget(
|
||||
1, self.icon_view if self.head_bar.view.isChecked() else self.list_view
|
||||
)
|
||||
|
||||
def toggle_view(self):
|
||||
self.settings.setValue("icon_view", not self.head_bar.view.isChecked())
|
||||
|
|
|
@ -44,9 +44,15 @@ class SaveWorker(QRunnable):
|
|||
def run(self) -> None:
|
||||
try:
|
||||
if isinstance(self.model, DownloadModel):
|
||||
shared.core.download_saves(self.model.app_name, self.model.latest_save.manifest_name, self.model.path)
|
||||
shared.core.download_saves(
|
||||
self.model.app_name,
|
||||
self.model.latest_save.manifest_name,
|
||||
self.model.path,
|
||||
)
|
||||
else:
|
||||
shared.core.upload_save(self.model.app_name, self.model.path, self.model.date_time)
|
||||
shared.core.upload_save(
|
||||
self.model.app_name, self.model.path, self.model.date_time
|
||||
)
|
||||
except Exception as e:
|
||||
self.signals.finished.emit(str(e), self.model.app_name)
|
||||
logger.error(str(e))
|
||||
|
@ -69,7 +75,13 @@ class CloudSaveDialog(QDialog, Ui_SyncSaveDialog):
|
|||
UPLOAD = 1
|
||||
CANCEL = 0
|
||||
|
||||
def __init__(self, igame: InstalledGame, dt_local: datetime.datetime, dt_remote: datetime.datetime, newer: str):
|
||||
def __init__(
|
||||
self,
|
||||
igame: InstalledGame,
|
||||
dt_local: datetime.datetime,
|
||||
dt_remote: datetime.datetime,
|
||||
newer: str,
|
||||
):
|
||||
super(CloudSaveDialog, self).__init__()
|
||||
self.setupUi(self)
|
||||
|
||||
|
@ -155,18 +167,34 @@ class CloudSaveUtils(QObject):
|
|||
self.core.lgd.set_installed_game(app_name, igame)
|
||||
logger.info(f"Set save path of {igame.title} to {savepath}")
|
||||
elif not ignore_settings: # sync on startup
|
||||
if QMessageBox.question(None, "Warning", self.tr(
|
||||
"Could not compute cloud save path. Please set it in Game settings manually. \nDo you want to launch {} anyway?").format(
|
||||
igame.title), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes:
|
||||
if (
|
||||
QMessageBox.question(
|
||||
None,
|
||||
"Warning",
|
||||
self.tr(
|
||||
"Could not compute cloud save path. Please set it in Game settings manually. \nDo you want to launch {} anyway?"
|
||||
).format(igame.title),
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No,
|
||||
)
|
||||
== QMessageBox.Yes
|
||||
):
|
||||
return False
|
||||
else:
|
||||
raise ValueError("No savepath detected")
|
||||
else:
|
||||
QMessageBox.warning(None, "Warning",
|
||||
self.tr("No savepath found. Please set it in Game Settings manually"))
|
||||
QMessageBox.warning(
|
||||
None,
|
||||
"Warning",
|
||||
self.tr(
|
||||
"No savepath found. Please set it in Game Settings manually"
|
||||
),
|
||||
)
|
||||
return False
|
||||
|
||||
res, (dt_local, dt_remote) = self.core.check_savegame_state(igame.save_path, self.latest_saves.get(app_name))
|
||||
res, (dt_local, dt_remote) = self.core.check_savegame_state(
|
||||
igame.save_path, self.latest_saves.get(app_name)
|
||||
)
|
||||
|
||||
if res == SaveGameStatus.NO_SAVE:
|
||||
return False
|
||||
|
@ -217,18 +245,27 @@ class CloudSaveUtils(QObject):
|
|||
self.core.lgd.set_installed_game(app_name, igame)
|
||||
logger.info(f"Set save path of {igame.title} to {savepath}")
|
||||
else:
|
||||
QMessageBox.warning(None, "Warning", self.tr("No savepath set. Skip syncing with cloud"))
|
||||
QMessageBox.warning(
|
||||
None, "Warning", self.tr("No savepath set. Skip syncing with cloud")
|
||||
)
|
||||
return False
|
||||
|
||||
res, (dt_local, dt_remote) = self.core.check_savegame_state(igame.save_path, self.latest_saves.get(app_name))
|
||||
res, (dt_local, dt_remote) = self.core.check_savegame_state(
|
||||
igame.save_path, self.latest_saves.get(app_name)
|
||||
)
|
||||
|
||||
if res == SaveGameStatus.LOCAL_NEWER and not always_ask:
|
||||
self.upload_saves(igame, dt_local)
|
||||
return
|
||||
|
||||
elif res == SaveGameStatus.NO_SAVE and not always_ask:
|
||||
QMessageBox.warning(None, "No saves", self.tr(
|
||||
"There are no saves local and online. Maybe you have to change save path of {}").format(igame.title))
|
||||
QMessageBox.warning(
|
||||
None,
|
||||
"No saves",
|
||||
self.tr(
|
||||
"There are no saves local and online. Maybe you have to change save path of {}"
|
||||
).format(igame.title),
|
||||
)
|
||||
self.sync_finished.emit(app_name)
|
||||
return
|
||||
|
||||
|
@ -256,7 +293,11 @@ class CloudSaveUtils(QObject):
|
|||
|
||||
def download_saves(self, igame):
|
||||
logger.info("Downloading saves for " + igame.title)
|
||||
w = SaveWorker(DownloadModel(igame.app_name, self.latest_saves.get(igame.app_name), igame.save_path))
|
||||
w = SaveWorker(
|
||||
DownloadModel(
|
||||
igame.app_name, self.latest_saves.get(igame.app_name), igame.save_path
|
||||
)
|
||||
)
|
||||
w.signals.finished.connect(self.worker_finished)
|
||||
self.thread_pool.start(w)
|
||||
|
||||
|
@ -266,5 +307,9 @@ class CloudSaveUtils(QObject):
|
|||
self.sync_finished.emit(app_name)
|
||||
self.latest_saves = self.get_latest_saves(shared.api_results.saves)
|
||||
else:
|
||||
QMessageBox.warning(None, "Warning", self.tr("Syncing with cloud failed: \n ") + error_message)
|
||||
QMessageBox.warning(
|
||||
None,
|
||||
"Warning",
|
||||
self.tr("Syncing with cloud failed: \n ") + error_message,
|
||||
)
|
||||
self.sync_finished.emit(app_name)
|
||||
|
|
|
@ -33,7 +33,10 @@ class GameInfoTabs(SideTabWidget):
|
|||
self.settings.update_game(app_name)
|
||||
|
||||
# DLC Tab: Disable if no dlcs available
|
||||
if len(self.dlc_list.get(self.core.get_game(app_name).catalog_item_id, [])) == 0:
|
||||
if (
|
||||
len(self.dlc_list.get(self.core.get_game(app_name).catalog_item_id, []))
|
||||
== 0
|
||||
):
|
||||
self.setTabEnabled(3, False)
|
||||
else:
|
||||
self.setTabEnabled(3, True)
|
||||
|
|
|
@ -69,11 +69,18 @@ class GameDlc(QWidget, Ui_GameDlc):
|
|||
|
||||
def install(self, app_name):
|
||||
if not self.core.is_installed(self.game.app_name):
|
||||
QMessageBox.warning(self, "Error", self.tr("Base Game is not installed. Please install {} first").format(
|
||||
self.game.app_title))
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Error",
|
||||
self.tr("Base Game is not installed. Please install {} first").format(
|
||||
self.game.app_title
|
||||
),
|
||||
)
|
||||
return
|
||||
|
||||
self.signals.install_game.emit(InstallOptionsModel(app_name=app_name, update=True))
|
||||
self.signals.install_game.emit(
|
||||
InstallOptionsModel(app_name=app_name, update=True)
|
||||
)
|
||||
|
||||
|
||||
class GameDlcWidget(QFrame, Ui_GameDlcWidget):
|
||||
|
@ -109,10 +116,15 @@ class GameDlcWidget(QFrame, Ui_GameDlcWidget):
|
|||
self.pixmap = a0
|
||||
self.image.setPixmap(
|
||||
self.pixmap.scaledToHeight(
|
||||
self.dlc_info.size().height() - (self.image.contentsMargins().top() +
|
||||
self.image.contentsMargins().bottom() +
|
||||
self.image.lineWidth()*2),
|
||||
Qt.SmoothTransformation))
|
||||
self.dlc_info.size().height()
|
||||
- (
|
||||
self.image.contentsMargins().top()
|
||||
+ self.image.contentsMargins().bottom()
|
||||
+ self.image.lineWidth() * 2
|
||||
),
|
||||
Qt.SmoothTransformation,
|
||||
)
|
||||
)
|
||||
self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
|
||||
|
||||
def uninstall_dlc(self):
|
||||
|
|
|
@ -58,20 +58,32 @@ class GameInfo(QWidget, Ui_GameInfo):
|
|||
self.uninstalled.emit(self.game.app_name)
|
||||
|
||||
def repair(self):
|
||||
repair_file = os.path.join(self.core.lgd.get_tmp_path(), f'{self.game.app_name}.repair')
|
||||
repair_file = os.path.join(
|
||||
self.core.lgd.get_tmp_path(), f"{self.game.app_name}.repair"
|
||||
)
|
||||
if not os.path.exists(repair_file):
|
||||
QMessageBox.warning(self, "Warning", self.tr(
|
||||
"Repair file does not exist or game does not need a repair. Please verify game first"))
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Warning",
|
||||
self.tr(
|
||||
"Repair file does not exist or game does not need a repair. Please verify game first"
|
||||
),
|
||||
)
|
||||
return
|
||||
self.signals.install_game.emit(InstallOptionsModel(app_name=self.game.app_name, repair=True,
|
||||
update=True))
|
||||
self.signals.install_game.emit(
|
||||
InstallOptionsModel(app_name=self.game.app_name, repair=True, update=True)
|
||||
)
|
||||
|
||||
def verify(self):
|
||||
if not os.path.exists(self.igame.install_path):
|
||||
logger.error("Path does not exist")
|
||||
QMessageBox.warning(self, "Warning",
|
||||
self.tr("Installation path of {} does not exist. Cannot verify").format(
|
||||
self.igame.title))
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Warning",
|
||||
self.tr("Installation path of {} does not exist. Cannot verify").format(
|
||||
self.igame.title
|
||||
),
|
||||
)
|
||||
return
|
||||
self.verify_widget.setCurrentIndex(1)
|
||||
verify_worker = VerifyWorker(self.core, self.game.app_name)
|
||||
|
@ -88,8 +100,11 @@ class GameInfo(QWidget, Ui_GameInfo):
|
|||
|
||||
def finish_verify(self, failed, missing, app_name):
|
||||
if failed == missing == 0:
|
||||
QMessageBox.information(self, "Summary",
|
||||
"Game was verified successfully. No missing or corrupt files found")
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Summary",
|
||||
"Game was verified successfully. No missing or corrupt files found",
|
||||
)
|
||||
igame = self.core.get_installed_game(app_name)
|
||||
if igame.needs_verification:
|
||||
igame.needs_verification = False
|
||||
|
@ -99,19 +114,28 @@ class GameInfo(QWidget, Ui_GameInfo):
|
|||
QMessageBox.warning(self, "Warning", self.tr("Something went wrong"))
|
||||
|
||||
else:
|
||||
ans = QMessageBox.question(self, "Summary", self.tr(
|
||||
'Verification failed, {} file(s) corrupted, {} file(s) are missing. Do you want to repair them?').format(
|
||||
failed, missing), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
|
||||
ans = QMessageBox.question(
|
||||
self,
|
||||
"Summary",
|
||||
self.tr(
|
||||
"Verification failed, {} file(s) corrupted, {} file(s) are missing. Do you want to repair them?"
|
||||
).format(failed, missing),
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.Yes,
|
||||
)
|
||||
if ans == QMessageBox.Yes:
|
||||
self.signals.install_game.emit(InstallOptionsModel(app_name=self.game.app_name, repair=True,
|
||||
update=True))
|
||||
self.signals.install_game.emit(
|
||||
InstallOptionsModel(
|
||||
app_name=self.game.app_name, repair=True, update=True
|
||||
)
|
||||
)
|
||||
self.verify_widget.setCurrentIndex(0)
|
||||
self.verify_threads.pop(app_name)
|
||||
|
||||
def update_game(self, app_name: str):
|
||||
self.game = self.core.get_game(app_name)
|
||||
self.igame = self.core.get_installed_game(self.game.app_name)
|
||||
self.game_title.setText(f'<h2>{self.game.app_title}</h2>')
|
||||
self.game_title.setText(f"<h2>{self.game.app_title}</h2>")
|
||||
|
||||
pixmap = get_pixmap(self.game.app_name)
|
||||
w = 200
|
||||
|
@ -122,7 +146,9 @@ class GameInfo(QWidget, Ui_GameInfo):
|
|||
if self.igame:
|
||||
self.version.setText(self.igame.version)
|
||||
else:
|
||||
self.version.setText(self.game.app_version(self.igame.platform if self.igame else "Windows"))
|
||||
self.version.setText(
|
||||
self.game.app_version(self.igame.platform if self.igame else "Windows")
|
||||
)
|
||||
self.dev.setText(self.game.metadata["developer"])
|
||||
|
||||
if self.igame:
|
||||
|
@ -154,10 +180,14 @@ class GameInfo(QWidget, Ui_GameInfo):
|
|||
self.steam_worker.set_app_name(self.game.app_name)
|
||||
QThreadPool.globalInstance().start(self.steam_worker)
|
||||
|
||||
if len(self.verify_threads.keys()) == 0 or not self.verify_threads.get(self.game.app_name):
|
||||
if len(self.verify_threads.keys()) == 0 or not self.verify_threads.get(
|
||||
self.game.app_name
|
||||
):
|
||||
self.verify_widget.setCurrentIndex(0)
|
||||
elif self.verify_threads.get(self.game.app_name):
|
||||
self.verify_widget.setCurrentIndex(1)
|
||||
self.verify_progress.setValue(
|
||||
self.verify_threads[self.game.app_name].num / self.verify_threads[self.game.app_name].total * 100
|
||||
self.verify_threads[self.game.app_name].num
|
||||
/ self.verify_threads[self.game.app_name].total
|
||||
* 100
|
||||
)
|
||||
|
|
|
@ -4,7 +4,14 @@ from logging import getLogger
|
|||
from typing import Tuple
|
||||
|
||||
from PyQt5.QtCore import QSettings, QThreadPool, Qt
|
||||
from PyQt5.QtWidgets import QWidget, QFileDialog, QMessageBox, QLabel, QPushButton, QSizePolicy
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget,
|
||||
QFileDialog,
|
||||
QMessageBox,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
)
|
||||
from qtawesome import icon
|
||||
|
||||
from legendary.core import LegendaryCore
|
||||
|
@ -23,7 +30,7 @@ def find_proton_wrappers():
|
|||
os.path.expanduser("~/.steam/steam/steamapps/common"),
|
||||
"/usr/share/steam/compatibilitytools.d",
|
||||
os.path.expanduser("~/.steam/compatibilitytools.d"),
|
||||
os.path.expanduser("~/.steam/root/compatibilitytools.d")
|
||||
os.path.expanduser("~/.steam/root/compatibilitytools.d"),
|
||||
]
|
||||
for c in compatibilitytools_dirs:
|
||||
if os.path.exists(c):
|
||||
|
@ -31,7 +38,9 @@ def find_proton_wrappers():
|
|||
proton = os.path.join(c, i, "proton")
|
||||
compatibilitytool = os.path.join(c, i, "compatibilitytool.vdf")
|
||||
toolmanifest = os.path.join(c, i, "toolmanifest.vdf")
|
||||
if os.path.exists(proton) and (os.path.exists(compatibilitytool) or os.path.exists(toolmanifest)):
|
||||
if os.path.exists(proton) and (
|
||||
os.path.exists(compatibilitytool) or os.path.exists(toolmanifest)
|
||||
):
|
||||
wrapper = '"' + proton + '" run'
|
||||
possible_proton_wrappers.append(wrapper)
|
||||
if not possible_proton_wrappers:
|
||||
|
@ -53,14 +62,23 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
self.core = core
|
||||
self.settings = QSettings()
|
||||
|
||||
self.cloud_save_path_edit = PathEdit("", file_type=QFileDialog.DirectoryOnly,
|
||||
ph_text=self.tr("Cloud save path"),
|
||||
edit_func=lambda text: (os.path.exists(text), text),
|
||||
save_func=self.save_save_path)
|
||||
self.cloud_gb.layout().addRow(QLabel(self.tr("Save path")), self.cloud_save_path_edit)
|
||||
self.cloud_save_path_edit = PathEdit(
|
||||
"",
|
||||
file_type=QFileDialog.DirectoryOnly,
|
||||
ph_text=self.tr("Cloud save path"),
|
||||
edit_func=lambda text: (os.path.exists(text), text),
|
||||
save_func=self.save_save_path,
|
||||
)
|
||||
self.cloud_gb.layout().addRow(
|
||||
QLabel(self.tr("Save path")), self.cloud_save_path_edit
|
||||
)
|
||||
|
||||
self.compute_save_path_button = QPushButton(icon("fa.magic"), self.tr("Auto compute save path"))
|
||||
self.compute_save_path_button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
|
||||
self.compute_save_path_button = QPushButton(
|
||||
icon("fa.magic"), self.tr("Auto compute save path")
|
||||
)
|
||||
self.compute_save_path_button.setSizePolicy(
|
||||
QSizePolicy.Maximum, QSizePolicy.Fixed
|
||||
)
|
||||
self.compute_save_path_button.clicked.connect(self.compute_save_path)
|
||||
self.cloud_gb.layout().addRow(None, self.compute_save_path_button)
|
||||
|
||||
|
@ -71,14 +89,14 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
lambda x: self.update_combobox(x, "skip_update_check")
|
||||
)
|
||||
self.cloud_sync.stateChanged.connect(
|
||||
lambda: self.settings.setValue(f"{self.game.app_name}/auto_sync_cloud", self.cloud_sync.isChecked())
|
||||
lambda: self.settings.setValue(
|
||||
f"{self.game.app_name}/auto_sync_cloud", self.cloud_sync.isChecked()
|
||||
)
|
||||
)
|
||||
self.launch_params.textChanged.connect(
|
||||
lambda x: self.save_line_edit("start_params", x)
|
||||
)
|
||||
self.wrapper.textChanged.connect(
|
||||
lambda x: self.save_line_edit("wrapper", x)
|
||||
)
|
||||
self.wrapper.textChanged.connect(lambda x: self.save_line_edit("wrapper", x))
|
||||
self.override_exe_edit.textChanged.connect(
|
||||
lambda x: self.save_line_edit("override_exe", x)
|
||||
)
|
||||
|
@ -92,7 +110,7 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
self.proton_prefix = PathEdit(
|
||||
file_type=QFileDialog.DirectoryOnly,
|
||||
edit_func=self.proton_prefix_edit,
|
||||
save_func=self.proton_prefix_save
|
||||
save_func=self.proton_prefix_save,
|
||||
)
|
||||
self.proton_prefix_layout.addWidget(self.proton_prefix)
|
||||
|
||||
|
@ -100,8 +118,10 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
# FIXME: Remove the spacerItem and margins from the linux settings
|
||||
# FIXME: This should be handled differently at soem point in the future
|
||||
self.linux_settings.layout().setContentsMargins(0, 0, 0, 0)
|
||||
for item in [self.linux_settings.layout().itemAt(idx) for idx in
|
||||
range(self.linux_settings.layout().count())]:
|
||||
for item in [
|
||||
self.linux_settings.layout().itemAt(idx)
|
||||
for idx in range(self.linux_settings.layout().count())
|
||||
]:
|
||||
if item.spacerItem():
|
||||
self.linux_settings.layout().removeItem(item)
|
||||
del item
|
||||
|
@ -113,7 +133,10 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
self.game_settings_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
def compute_save_path(self):
|
||||
if self.core.is_installed(self.game.app_name) and self.game.supports_cloud_saves:
|
||||
if (
|
||||
self.core.is_installed(self.game.app_name)
|
||||
and self.game.supports_cloud_saves
|
||||
):
|
||||
try:
|
||||
new_path = self.core.get_save_path(self.game.app_name)
|
||||
except Exception as e:
|
||||
|
@ -121,16 +144,22 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
self.cloud_save_path_edit.setText(self.tr("Loading"))
|
||||
self.cloud_save_path_edit.setDisabled(True)
|
||||
self.compute_save_path_button.setDisabled(True)
|
||||
resolver = WineResolver(get_raw_save_path(self.game), self.game.app_name, self.core)
|
||||
resolver = WineResolver(
|
||||
get_raw_save_path(self.game), self.game.app_name, self.core
|
||||
)
|
||||
app_name = self.game.app_name[:]
|
||||
resolver.signals.result_ready.connect(lambda x: self.wine_resolver_finished(x, app_name))
|
||||
resolver.signals.result_ready.connect(
|
||||
lambda x: self.wine_resolver_finished(x, app_name)
|
||||
)
|
||||
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}")
|
||||
logger.info(
|
||||
f"Wine resolver finished for {app_name}. Computed save path: {path}"
|
||||
)
|
||||
if app_name == self.game.app_name:
|
||||
self.cloud_save_path_edit.setDisabled(False)
|
||||
self.compute_save_path_button.setDisabled(False)
|
||||
|
@ -139,9 +168,13 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
os.makedirs(path)
|
||||
except PermissionError:
|
||||
self.cloud_save_path_edit.setText("")
|
||||
QMessageBox.warning(None, "Error", self.tr(
|
||||
"Error while launching {}. No permission to create {}").format(
|
||||
self.game.app_title, path))
|
||||
QMessageBox.warning(
|
||||
None,
|
||||
"Error",
|
||||
self.tr(
|
||||
"Error while launching {}. No permission to create {}"
|
||||
).format(self.game.app_title, path),
|
||||
)
|
||||
return
|
||||
if not path:
|
||||
self.cloud_save_path_edit.setText("")
|
||||
|
@ -163,8 +196,13 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
self.core.lgd.config.add_section(self.game.app_name)
|
||||
self.core.lgd.config.set(self.game.app_name, option, value)
|
||||
else:
|
||||
if self.core.lgd.config.has_section(self.game.app_name) and self.core.lgd.config.get(
|
||||
f"{self.game.app_name}", option, fallback=None) is not None:
|
||||
if (
|
||||
self.core.lgd.config.has_section(self.game.app_name)
|
||||
and self.core.lgd.config.get(
|
||||
f"{self.game.app_name}", option, fallback=None
|
||||
)
|
||||
is not None
|
||||
):
|
||||
self.core.lgd.config.remove_option(self.game.app_name, option)
|
||||
if not self.core.lgd.config[self.game.app_name]:
|
||||
self.core.lgd.config.remove_section(self.game.app_name)
|
||||
|
@ -186,7 +224,9 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
self.core.lgd.config.set(self.game.app_name, option, "false")
|
||||
else:
|
||||
if self.game.app_name in self.core.lgd.config.sections():
|
||||
if self.core.lgd.config.get(f"{self.game.app_name}", option, fallback=False):
|
||||
if self.core.lgd.config.get(
|
||||
f"{self.game.app_name}", option, fallback=False
|
||||
):
|
||||
self.core.lgd.config.remove_option(self.game.app_name, option)
|
||||
if not self.core.lgd.config[self.game.app_name]:
|
||||
self.core.lgd.config.remove_section(self.game.app_name)
|
||||
|
@ -197,21 +237,39 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
# Dont use Proton
|
||||
if i == 0:
|
||||
if f"{self.game.app_name}" in self.core.lgd.config.sections():
|
||||
if self.core.lgd.config.get(f"{self.game.app_name}", "wrapper", fallback=False):
|
||||
self.core.lgd.config.remove_option(self.game.app_name, "wrapper")
|
||||
if self.core.lgd.config.get(f"{self.game.app_name}", "no_wine", fallback=False):
|
||||
self.core.lgd.config.remove_option(self.game.app_name, "no_wine")
|
||||
if self.core.lgd.config.get(
|
||||
f"{self.game.app_name}", "wrapper", fallback=False
|
||||
):
|
||||
self.core.lgd.config.remove_option(
|
||||
self.game.app_name, "wrapper"
|
||||
)
|
||||
if self.core.lgd.config.get(
|
||||
f"{self.game.app_name}", "no_wine", fallback=False
|
||||
):
|
||||
self.core.lgd.config.remove_option(
|
||||
self.game.app_name, "no_wine"
|
||||
)
|
||||
if not self.core.lgd.config[self.game.app_name]:
|
||||
self.core.lgd.config.remove_section(self.game.app_name)
|
||||
if f"{self.game.app_name}.env" in self.core.lgd.config.sections():
|
||||
if self.core.lgd.config.get(f"{self.game.app_name}.env", "STEAM_COMPAT_DATA_PATH", fallback=False):
|
||||
self.core.lgd.config.remove_option(f"{self.game.app_name}.env", "STEAM_COMPAT_DATA_PATH")
|
||||
if self.core.lgd.config.get(
|
||||
f"{self.game.app_name}.env",
|
||||
"STEAM_COMPAT_DATA_PATH",
|
||||
fallback=False,
|
||||
):
|
||||
self.core.lgd.config.remove_option(
|
||||
f"{self.game.app_name}.env", "STEAM_COMPAT_DATA_PATH"
|
||||
)
|
||||
if not self.core.lgd.config[self.game.app_name + ".env"]:
|
||||
self.core.lgd.config.remove_section(self.game.app_name + ".env")
|
||||
self.proton_prefix.setEnabled(False)
|
||||
# lk: TODO: This has to be fixed properly.
|
||||
# lk: It happens because of the widget update. Mask it for now behind disabling the save button
|
||||
self.wrapper.setText(self.core.lgd.config.get(f"{self.game.app_name}", "wrapper", fallback=""))
|
||||
self.wrapper.setText(
|
||||
self.core.lgd.config.get(
|
||||
f"{self.game.app_name}", "wrapper", fallback=""
|
||||
)
|
||||
)
|
||||
self.wrapper.setEnabled(True)
|
||||
self.linux_settings.wine_groupbox.setEnabled(True)
|
||||
else:
|
||||
|
@ -225,8 +283,11 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
self.core.lgd.config[self.game.app_name + ".env"] = {}
|
||||
self.core.lgd.config.set(self.game.app_name, "wrapper", wrapper)
|
||||
self.core.lgd.config.set(self.game.app_name, "no_wine", "true")
|
||||
self.core.lgd.config.set(self.game.app_name + ".env", "STEAM_COMPAT_DATA_PATH",
|
||||
os.path.expanduser("~/.proton"))
|
||||
self.core.lgd.config.set(
|
||||
self.game.app_name + ".env",
|
||||
"STEAM_COMPAT_DATA_PATH",
|
||||
os.path.expanduser("~/.proton"),
|
||||
)
|
||||
self.proton_prefix.setText(os.path.expanduser("~/.proton"))
|
||||
|
||||
# Dont use Wine
|
||||
|
@ -243,7 +304,9 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
return os.path.exists(parent), text
|
||||
|
||||
def proton_prefix_save(self, text: str):
|
||||
self.core.lgd.config.set(self.game.app_name + ".env", "STEAM_COMPAT_DATA_PATH", text)
|
||||
self.core.lgd.config.set(
|
||||
self.game.app_name + ".env", "STEAM_COMPAT_DATA_PATH", text
|
||||
)
|
||||
self.core.lgd.save_config()
|
||||
|
||||
def update_game(self, app_name: str):
|
||||
|
@ -252,7 +315,9 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
self.igame = self.core.get_installed_game(self.game.app_name)
|
||||
if self.igame:
|
||||
if self.igame.can_run_offline:
|
||||
offline = self.core.lgd.config.get(self.game.app_name, "offline", fallback="unset")
|
||||
offline = self.core.lgd.config.get(
|
||||
self.game.app_name, "offline", fallback="unset"
|
||||
)
|
||||
if offline == "true":
|
||||
self.offline.setCurrentIndex(1)
|
||||
elif offline == "false":
|
||||
|
@ -266,7 +331,9 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
else:
|
||||
self.offline.setEnabled(False)
|
||||
|
||||
skip_update = self.core.lgd.config.get(self.game.app_name, "skip_update_check", fallback="unset")
|
||||
skip_update = self.core.lgd.config.get(
|
||||
self.game.app_name, "skip_update_check", fallback="unset"
|
||||
)
|
||||
if skip_update == "true":
|
||||
self.skip_update.setCurrentIndex(1)
|
||||
elif skip_update == "false":
|
||||
|
@ -286,13 +353,19 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
else:
|
||||
self.linux_settings_scroll.setVisible(True)
|
||||
|
||||
proton = self.core.lgd.config.get(f"{app_name}", "wrapper", fallback="").replace('"', "")
|
||||
proton = self.core.lgd.config.get(
|
||||
f"{app_name}", "wrapper", fallback=""
|
||||
).replace('"', "")
|
||||
if proton and "proton" in proton:
|
||||
self.proton_prefix.setEnabled(True)
|
||||
self.proton_wrapper.setCurrentText(f'"{proton.replace(" run", "")}" run')
|
||||
proton_prefix = self.core.lgd.config.get(f"{app_name}.env", "STEAM_COMPAT_DATA_PATH",
|
||||
fallback=self.tr(
|
||||
"Please select path for proton prefix"))
|
||||
self.proton_wrapper.setCurrentText(
|
||||
f'"{proton.replace(" run", "")}" run'
|
||||
)
|
||||
proton_prefix = self.core.lgd.config.get(
|
||||
f"{app_name}.env",
|
||||
"STEAM_COMPAT_DATA_PATH",
|
||||
fallback=self.tr("Please select path for proton prefix"),
|
||||
)
|
||||
self.proton_prefix.setText(proton_prefix)
|
||||
self.wrapper.setEnabled(False)
|
||||
else:
|
||||
|
@ -305,15 +378,21 @@ class GameSettings(QWidget, Ui_GameSettings):
|
|||
self.cloud_save_path_edit.setText("")
|
||||
else:
|
||||
self.cloud_gb.setEnabled(True)
|
||||
sync_cloud = self.settings.value(f"{self.game.app_name}/auto_sync_cloud", True, bool)
|
||||
sync_cloud = self.settings.value(
|
||||
f"{self.game.app_name}/auto_sync_cloud", True, bool
|
||||
)
|
||||
self.cloud_sync.setChecked(sync_cloud)
|
||||
if self.igame.save_path:
|
||||
self.cloud_save_path_edit.setText(self.igame.save_path)
|
||||
else:
|
||||
self.cloud_save_path_edit.setText("")
|
||||
|
||||
self.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.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.change = True
|
||||
|
||||
|
||||
|
|
|
@ -56,10 +56,15 @@ class GameUtils(QObject):
|
|||
game = self.core.get_game(app_name)
|
||||
igame = self.core.get_installed_game(app_name)
|
||||
if not os.path.exists(igame.install_path):
|
||||
if QMessageBox.Yes == QMessageBox.question(None, "Uninstall", self.tr(
|
||||
"Game files of {} do not exist. Remove it from installed games?").format(igame.title),
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.Yes):
|
||||
if QMessageBox.Yes == QMessageBox.question(
|
||||
None,
|
||||
"Uninstall",
|
||||
self.tr(
|
||||
"Game files of {} do not exist. Remove it from installed games?"
|
||||
).format(igame.title),
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.Yes,
|
||||
):
|
||||
self.core.lgd.remove_installed_game(app_name)
|
||||
return True
|
||||
else:
|
||||
|
@ -72,7 +77,9 @@ class GameUtils(QObject):
|
|||
shared.signals.game_uninstalled.emit(app_name)
|
||||
return True
|
||||
|
||||
def prepare_launch(self, app_name, offline: bool = False, skip_update_check: bool = False):
|
||||
def prepare_launch(
|
||||
self, app_name, offline: bool = False, skip_update_check: bool = False
|
||||
):
|
||||
game = self.core.get_game(app_name)
|
||||
dont_sync_after_finish = False
|
||||
|
||||
|
@ -90,10 +97,19 @@ class GameUtils(QObject):
|
|||
return
|
||||
self.sync_finished(app_name)
|
||||
|
||||
self.launch_game(app_name, offline, skip_update_check, ask_always_sync=dont_sync_after_finish)
|
||||
self.launch_game(
|
||||
app_name, offline, skip_update_check, ask_always_sync=dont_sync_after_finish
|
||||
)
|
||||
|
||||
def launch_game(self, app_name: str, offline: bool = False, skip_update_check: bool = False, wine_bin: str = None,
|
||||
wine_pfx: str = None, ask_always_sync: bool = False):
|
||||
def launch_game(
|
||||
self,
|
||||
app_name: str,
|
||||
offline: bool = False,
|
||||
skip_update_check: bool = False,
|
||||
wine_bin: str = None,
|
||||
wine_pfx: str = None,
|
||||
ask_always_sync: bool = False,
|
||||
):
|
||||
if shared.args.offline:
|
||||
offline = True
|
||||
game = self.core.get_game(app_name)
|
||||
|
@ -105,8 +121,15 @@ class GameUtils(QObject):
|
|||
return
|
||||
|
||||
if QSettings().value("confirm_start", False, bool):
|
||||
if not QMessageBox.question(None, "Launch", self.tr("Do you want to launch {}").format(game.app_title),
|
||||
QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
|
||||
if (
|
||||
not QMessageBox.question(
|
||||
None,
|
||||
"Launch",
|
||||
self.tr("Do you want to launch {}").format(game.app_title),
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
)
|
||||
== QMessageBox.Yes
|
||||
):
|
||||
logger.info("Cancel Startup")
|
||||
self.finished.emit(app_name, "")
|
||||
return
|
||||
|
@ -119,12 +142,18 @@ class GameUtils(QObject):
|
|||
logger.error(f"{app_name} is not installed")
|
||||
if game.is_dlc:
|
||||
logger.error("Game is dlc")
|
||||
self.finished.emit(app_name, self.tr("Game is a DLC. Please launch base game instead"))
|
||||
self.finished.emit(
|
||||
app_name, self.tr("Game is a DLC. Please launch base game instead")
|
||||
)
|
||||
return
|
||||
if not os.path.exists(igame.install_path):
|
||||
logger.error("Game doesn't exist")
|
||||
self.finished.emit(app_name,
|
||||
self.tr("Game files of {} do not exist. Please install game").format(game.app_title))
|
||||
self.finished.emit(
|
||||
app_name,
|
||||
self.tr(
|
||||
"Game files of {} do not exist. Please install game"
|
||||
).format(game.app_title),
|
||||
)
|
||||
return
|
||||
|
||||
process = GameProcess(app_name)
|
||||
|
@ -135,7 +164,9 @@ class GameUtils(QObject):
|
|||
if not skip_update_check and not self.core.is_noupdate_game(app_name):
|
||||
# check updates
|
||||
try:
|
||||
latest = self.core.get_asset(app_name, igame.platform, update=False)
|
||||
latest = self.core.get_asset(
|
||||
app_name, igame.platform, update=False
|
||||
)
|
||||
except ValueError:
|
||||
self.finished.emit(app_name, self.tr("Metadata doesn't exist"))
|
||||
return
|
||||
|
@ -144,12 +175,15 @@ class GameUtils(QObject):
|
|||
self.finished.emit(app_name, self.tr("Please update game"))
|
||||
return
|
||||
|
||||
params: LaunchParameters = self.core.get_launch_parameters(app_name=app_name, offline=offline,
|
||||
wine_bin=wine_bin, wine_pfx=wine_pfx)
|
||||
params: LaunchParameters = self.core.get_launch_parameters(
|
||||
app_name=app_name, offline=offline, wine_bin=wine_bin, wine_pfx=wine_pfx
|
||||
)
|
||||
|
||||
full_params = list()
|
||||
full_params.extend(params.launch_command)
|
||||
full_params.append(os.path.join(params.game_directory, params.game_executable))
|
||||
full_params.append(
|
||||
os.path.join(params.game_directory, params.game_executable)
|
||||
)
|
||||
full_params.extend(params.game_parameters)
|
||||
full_params.extend(params.egl_parameters)
|
||||
full_params.extend(params.user_parameters)
|
||||
|
@ -170,23 +204,33 @@ class GameUtils(QObject):
|
|||
os.makedirs(val)
|
||||
except PermissionError as e:
|
||||
logger.error(str(e))
|
||||
QMessageBox.warning(None, "Error",
|
||||
self.tr(
|
||||
"Error while launching {}. No permission to create {} for {}").format(
|
||||
game.app_title, val, env))
|
||||
QMessageBox.warning(
|
||||
None,
|
||||
"Error",
|
||||
self.tr(
|
||||
"Error while launching {}. No permission to create {} for {}"
|
||||
).format(game.app_title, val, env),
|
||||
)
|
||||
process.deleteLater()
|
||||
return
|
||||
# check wine executable
|
||||
if shutil.which(full_params[0]) is None:
|
||||
# wine binary does not exist
|
||||
QMessageBox.warning(None, "Warning", self.tr(
|
||||
"Wine executable '{}' does not exist. Please change it in Settings").format(full_params[0]))
|
||||
QMessageBox.warning(
|
||||
None,
|
||||
"Warning",
|
||||
self.tr(
|
||||
"Wine executable '{}' does not exist. Please change it in Settings"
|
||||
).format(full_params[0]),
|
||||
)
|
||||
process.deleteLater()
|
||||
return
|
||||
|
||||
process.setProcessEnvironment(environment)
|
||||
process.game_finished.connect(self.game_finished)
|
||||
running_game = RunningGameModel(process=process, app_name=app_name, always_ask_sync=ask_always_sync)
|
||||
running_game = RunningGameModel(
|
||||
process=process, app_name=app_name, always_ask_sync=ask_always_sync
|
||||
)
|
||||
|
||||
process.start(full_params[0], full_params[1:])
|
||||
self.game_launched.emit(app_name)
|
||||
|
@ -201,25 +245,37 @@ class GameUtils(QObject):
|
|||
webbrowser.open(origin_uri)
|
||||
self.finished.emit(app_name, "")
|
||||
return
|
||||
wine_pfx = self.core.lgd.config.get(game.app_name, 'wine_prefix',
|
||||
fallback=os.path.expanduser("~/.wine"))
|
||||
wine_pfx = self.core.lgd.config.get(
|
||||
game.app_name, "wine_prefix", fallback=os.path.expanduser("~/.wine")
|
||||
)
|
||||
if not wine_bin:
|
||||
wine_bin = self.core.lgd.config.get(game.app_name, 'wine_executable', fallback="/usr/bin/wine")
|
||||
wine_bin = self.core.lgd.config.get(
|
||||
game.app_name, "wine_executable", fallback="/usr/bin/wine"
|
||||
)
|
||||
|
||||
if shutil.which(wine_bin) is None:
|
||||
# wine binary does not exist
|
||||
QMessageBox.warning(None, "Warning",
|
||||
self.tr("Wine executable '{}' does not exist. Please change it in Settings").format(
|
||||
wine_bin))
|
||||
QMessageBox.warning(
|
||||
None,
|
||||
"Warning",
|
||||
self.tr(
|
||||
"Wine executable '{}' does not exist. Please change it in Settings"
|
||||
).format(wine_bin),
|
||||
)
|
||||
process.deleteLater()
|
||||
return
|
||||
|
||||
env = self.core.get_app_environment(game.app_name, wine_pfx=wine_pfx)
|
||||
|
||||
if not env.get('WINEPREFIX') and not os.path.exists("/usr/bin/wine"):
|
||||
logger.error(f'In order to launch Origin correctly you must specify the wine binary and prefix '
|
||||
f'to use in the configuration file or command line. See the README for details.')
|
||||
self.finished.emit(app_name, self.tr("No wine executable selected. Please set it in settings"))
|
||||
if not env.get("WINEPREFIX") and not os.path.exists("/usr/bin/wine"):
|
||||
logger.error(
|
||||
f"In order to launch Origin correctly you must specify the wine binary and prefix "
|
||||
f"to use in the configuration file or command line. See the README for details."
|
||||
)
|
||||
self.finished.emit(
|
||||
app_name,
|
||||
self.tr("No wine executable selected. Please set it in settings"),
|
||||
)
|
||||
return
|
||||
|
||||
environment = QProcessEnvironment()
|
||||
|
@ -231,17 +287,27 @@ class GameUtils(QObject):
|
|||
|
||||
if QSettings().value("show_console", False, bool):
|
||||
self.console.show()
|
||||
process.readyReadStandardOutput.connect(lambda: self.console.log(
|
||||
str(process.readAllStandardOutput().data(), "utf-8", "ignore")))
|
||||
process.readyReadStandardError.connect(lambda: self.console.error(
|
||||
str(process.readAllStandardError().data(), "utf-8", "ignore")))
|
||||
process.readyReadStandardOutput.connect(
|
||||
lambda: self.console.log(
|
||||
str(process.readAllStandardOutput().data(), "utf-8", "ignore")
|
||||
)
|
||||
)
|
||||
process.readyReadStandardError.connect(
|
||||
lambda: self.console.error(
|
||||
str(process.readAllStandardError().data(), "utf-8", "ignore")
|
||||
)
|
||||
)
|
||||
|
||||
def game_finished(self, exit_code, app_name):
|
||||
logger.info("Game exited with exit code: " + str(exit_code))
|
||||
is_origin = self.core.get_game(app_name).third_party_store == "Origin"
|
||||
if exit_code == 53 and is_origin:
|
||||
msg_box = QMessageBox()
|
||||
msg_box.setText(self.tr("Origin is not installed. Do you want to download installer file? "))
|
||||
msg_box.setText(
|
||||
self.tr(
|
||||
"Origin is not installed. Do you want to download installer file? "
|
||||
)
|
||||
)
|
||||
msg_box.addButton(QPushButton("Download"), QMessageBox.YesRole)
|
||||
msg_box.addButton(QPushButton("Cancel"), QMessageBox.RejectRole)
|
||||
resp = msg_box.exec()
|
||||
|
@ -249,8 +315,13 @@ class GameUtils(QObject):
|
|||
if resp == 0:
|
||||
webbrowser.open("https://www.dm.origin.com/download")
|
||||
if is_origin and exit_code == 1:
|
||||
QMessageBox.warning(None, "Warning",
|
||||
self.tr("Failed to launch {}").format(self.core.get_game(app_name).app_title))
|
||||
QMessageBox.warning(
|
||||
None,
|
||||
"Warning",
|
||||
self.tr("Failed to launch {}").format(
|
||||
self.core.get_game(app_name).app_title
|
||||
),
|
||||
)
|
||||
|
||||
game: RunningGameModel = self.running_games.get(app_name, None)
|
||||
if app_name in self.running_games.keys():
|
||||
|
@ -262,10 +333,15 @@ class GameUtils(QObject):
|
|||
|
||||
if self.core.get_game(app_name).supports_cloud_saves:
|
||||
if exit_code != 0:
|
||||
r = QMessageBox.question(None, "Question", self.tr(
|
||||
"Game exited with code {}, which is not a normal code. It could be caused by a crash. Do you want to sync cloud saves").format(
|
||||
exit_code),
|
||||
buttons=QMessageBox.Yes | QMessageBox.No, defaultButton=QMessageBox.Yes)
|
||||
r = QMessageBox.question(
|
||||
None,
|
||||
"Question",
|
||||
self.tr(
|
||||
"Game exited with code {}, which is not a normal code. It could be caused by a crash. Do you want to sync cloud saves"
|
||||
).format(exit_code),
|
||||
buttons=QMessageBox.Yes | QMessageBox.No,
|
||||
defaultButton=QMessageBox.Yes,
|
||||
)
|
||||
if r != QMessageBox.Yes:
|
||||
return
|
||||
self.cloud_save_utils.game_finished(app_name, game.always_ask_sync)
|
||||
|
|
|
@ -33,27 +33,30 @@ class BaseInstalledWidget(QGroupBox):
|
|||
"update_available": self.tr("Start game without version check"),
|
||||
"launch": self.tr("Launch Game"),
|
||||
"launch_origin": self.tr("Launch/Link"),
|
||||
"running": self.tr("Game running")
|
||||
"running": self.tr("Game running"),
|
||||
},
|
||||
"default": {
|
||||
"running": self.tr("Game running"),
|
||||
"syncing": self.tr("Syncing cloud saves"),
|
||||
"update_available": self.tr("Update available")
|
||||
}
|
||||
"update_available": self.tr("Update available"),
|
||||
},
|
||||
}
|
||||
|
||||
self.game = self.core.get_game(app_name)
|
||||
self.igame = self.core.get_installed_game(app_name) # None if origin
|
||||
|
||||
self.image = QLabel()
|
||||
self.image.setPixmap(pixmap.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation))
|
||||
self.image.setPixmap(
|
||||
pixmap.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation)
|
||||
)
|
||||
self.game_running = False
|
||||
self.offline = shared.args.offline
|
||||
self.update_available = False
|
||||
if self.igame and self.core.lgd.assets:
|
||||
try:
|
||||
remote_version = self.core.get_asset(self.game.app_name, platform=self.igame.platform,
|
||||
update=False).build_version
|
||||
remote_version = self.core.get_asset(
|
||||
self.game.app_name, platform=self.igame.platform, update=False
|
||||
).build_version
|
||||
except ValueError:
|
||||
logger.error("Asset error for " + self.game.app_title)
|
||||
self.update_available = False
|
||||
|
@ -75,19 +78,26 @@ class BaseInstalledWidget(QGroupBox):
|
|||
sync.triggered.connect(self.sync_game)
|
||||
self.addAction(sync)
|
||||
|
||||
if os.path.exists(os.path.expanduser(f"~/Desktop/{self.game.app_title}.desktop")) \
|
||||
or os.path.exists(os.path.expanduser(f"~/Desktop/{self.game.app_title}.lnk")):
|
||||
if os.path.exists(
|
||||
os.path.expanduser(f"~/Desktop/{self.game.app_title}.desktop")
|
||||
) or os.path.exists(os.path.expanduser(f"~/Desktop/{self.game.app_title}.lnk")):
|
||||
self.create_desktop = QAction(self.tr("Remove Desktop link"))
|
||||
else:
|
||||
self.create_desktop = QAction(self.tr("Create Desktop link"))
|
||||
if self.igame:
|
||||
self.create_desktop.triggered.connect(lambda: self.create_desktop_link("desktop"))
|
||||
self.create_desktop.triggered.connect(
|
||||
lambda: self.create_desktop_link("desktop")
|
||||
)
|
||||
self.addAction(self.create_desktop)
|
||||
|
||||
if platform.system() == "Linux":
|
||||
start_menu_file = os.path.expanduser(f"~/.local/share/applications/{self.game.app_title}.desktop")
|
||||
start_menu_file = os.path.expanduser(
|
||||
f"~/.local/share/applications/{self.game.app_title}.desktop"
|
||||
)
|
||||
elif platform.system() == "Windows":
|
||||
start_menu_file = os.path.expandvars("%appdata%/Microsoft/Windows/Start Menu")
|
||||
start_menu_file = os.path.expandvars(
|
||||
"%appdata%/Microsoft/Windows/Start Menu"
|
||||
)
|
||||
else:
|
||||
start_menu_file = ""
|
||||
if platform.system() in ["Windows", "Linux"]:
|
||||
|
@ -96,7 +106,9 @@ class BaseInstalledWidget(QGroupBox):
|
|||
else:
|
||||
self.create_start_menu = QAction(self.tr("Create start menu link"))
|
||||
if self.igame:
|
||||
self.create_start_menu.triggered.connect(lambda: self.create_desktop_link("start_menu"))
|
||||
self.create_start_menu.triggered.connect(
|
||||
lambda: self.create_desktop_link("start_menu")
|
||||
)
|
||||
self.addAction(self.create_start_menu)
|
||||
|
||||
reload_image = QAction(self.tr("Reload Image"), self)
|
||||
|
@ -105,19 +117,26 @@ class BaseInstalledWidget(QGroupBox):
|
|||
|
||||
uninstall = QAction(self.tr("Uninstall"), self)
|
||||
uninstall.triggered.connect(
|
||||
lambda: shared.signals.update_gamelist.emit([self.game.app_name]) if self.game_utils.uninstall_game(
|
||||
self.game.app_name) else None)
|
||||
lambda: shared.signals.update_gamelist.emit([self.game.app_name])
|
||||
if self.game_utils.uninstall_game(self.game.app_name)
|
||||
else None
|
||||
)
|
||||
self.addAction(uninstall)
|
||||
|
||||
def reload_image(self):
|
||||
utils.download_image(self.game, True)
|
||||
pm = utils.get_pixmap(self.game.app_name)
|
||||
self.image.setPixmap(pm.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation))
|
||||
self.image.setPixmap(
|
||||
pm.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation)
|
||||
)
|
||||
|
||||
def create_desktop_link(self, type_of_link):
|
||||
if platform.system() not in ["Windows", "Linux"]:
|
||||
QMessageBox.warning(self, "Warning",
|
||||
f"Create a Desktop link is currently not supported on {platform.system()}")
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Warning",
|
||||
f"Create a Desktop link is currently not supported on {platform.system()}",
|
||||
)
|
||||
return
|
||||
if type_of_link == "desktop":
|
||||
path = os.path.expanduser(f"~/Desktop/")
|
||||
|
@ -125,19 +144,25 @@ class BaseInstalledWidget(QGroupBox):
|
|||
path = os.path.expanduser("~/.local/share/applications/")
|
||||
else:
|
||||
return
|
||||
if not (os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.desktop"))
|
||||
or os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.lnk"))):
|
||||
if not (
|
||||
os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.desktop"))
|
||||
or os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.lnk"))
|
||||
):
|
||||
try:
|
||||
if not create_desktop_link(self.game.app_name, self.core, type_of_link):
|
||||
return
|
||||
except PermissionError:
|
||||
QMessageBox.warning(self, "Error", "Permission error. Cannot create Desktop Link")
|
||||
QMessageBox.warning(
|
||||
self, "Error", "Permission error. Cannot create Desktop Link"
|
||||
)
|
||||
if type_of_link == "desktop":
|
||||
self.create_desktop.setText(self.tr("Remove Desktop link"))
|
||||
elif type_of_link == "start_menu":
|
||||
self.create_start_menu.setText(self.tr("Remove Start menu link"))
|
||||
else:
|
||||
if os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.desktop")):
|
||||
if os.path.exists(
|
||||
os.path.expanduser(f"{path}{self.game.app_title}.desktop")
|
||||
):
|
||||
os.remove(os.path.expanduser(f"{path}{self.game.app_title}.desktop"))
|
||||
elif os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.lnk")):
|
||||
os.remove(os.path.expanduser(f"{path}{self.game.app_title}.lnk"))
|
||||
|
@ -151,14 +176,18 @@ class BaseInstalledWidget(QGroupBox):
|
|||
if not self.game_running:
|
||||
if self.game.supports_cloud_saves:
|
||||
self.syncing_cloud_saves = True
|
||||
self.game_utils.prepare_launch(self.game.app_name, offline, skip_version_check)
|
||||
self.game_utils.prepare_launch(
|
||||
self.game.app_name, offline, skip_version_check
|
||||
)
|
||||
|
||||
def sync_finished(self, app_name):
|
||||
self.syncing_cloud_saves = False
|
||||
|
||||
def sync_game(self):
|
||||
|
||||
if self.game_utils.cloud_save_utils.sync_before_launch_game(self.game.app_name, True):
|
||||
if self.game_utils.cloud_save_utils.sync_before_launch_game(
|
||||
self.game.app_name, True
|
||||
):
|
||||
self.syncing_cloud_saves = True
|
||||
|
||||
def game_finished(self, app_name, error):
|
||||
|
|
|
@ -17,7 +17,9 @@ class BaseUninstalledWidget(QGroupBox):
|
|||
self.game = game
|
||||
self.core = core
|
||||
self.image = QLabel()
|
||||
self.image.setPixmap(pixmap.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation))
|
||||
self.image.setPixmap(
|
||||
pixmap.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation)
|
||||
)
|
||||
self.installing = False
|
||||
self.setContextMenuPolicy(Qt.ActionsContextMenu)
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
|
@ -29,7 +31,9 @@ class BaseUninstalledWidget(QGroupBox):
|
|||
def reload_image(self):
|
||||
utils.download_image(self.game, True)
|
||||
pm = utils.get_uninstalled_pixmap(self.game.app_name)
|
||||
self.image.setPixmap(pm.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation))
|
||||
self.image.setPixmap(
|
||||
pm.scaled(200, int(200 * 4 / 3), transformMode=Qt.SmoothTransformation)
|
||||
)
|
||||
|
||||
def install(self):
|
||||
self.show_uninstalled_info.emit(self.game)
|
||||
|
|
|
@ -6,7 +6,9 @@ from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QPushButton, QLabel
|
|||
from qtawesome import icon
|
||||
|
||||
from rare import shared
|
||||
from rare.components.tabs.games.game_widgets.base_installed_widget import BaseInstalledWidget
|
||||
from rare.components.tabs.games.game_widgets.base_installed_widget import (
|
||||
BaseInstalledWidget,
|
||||
)
|
||||
|
||||
logger = getLogger("GameWidgetInstalled")
|
||||
|
||||
|
@ -43,7 +45,9 @@ class InstalledIconWidget(BaseInstalledWidget):
|
|||
self.menu_btn.setIcon(icon("ei.info-circle"))
|
||||
# self.menu_btn.setObjectName("installed_menu_button")
|
||||
self.menu_btn.setIconSize(QSize(18, 18))
|
||||
self.menu_btn.enterEvent = lambda x: self.info_label.setText(self.tr("Information"))
|
||||
self.menu_btn.enterEvent = lambda x: self.info_label.setText(
|
||||
self.tr("Information")
|
||||
)
|
||||
self.menu_btn.leaveEvent = lambda x: self.enterEvent(None)
|
||||
# remove Border
|
||||
|
||||
|
@ -77,7 +81,9 @@ class InstalledIconWidget(BaseInstalledWidget):
|
|||
elif self.update_available:
|
||||
self.info_label.setText(self.texts["hover"]["update_available"])
|
||||
else:
|
||||
self.info_label.setText(self.texts["hover"]["launch" if self.igame else "launch_origin"])
|
||||
self.info_label.setText(
|
||||
self.texts["hover"]["launch" if self.igame else "launch_origin"]
|
||||
)
|
||||
|
||||
def leaveEvent(self, a0: QEvent = None) -> None:
|
||||
if self.game_running:
|
||||
|
|
|
@ -4,7 +4,9 @@ from PyQt5.QtCore import QProcess, pyqtSignal, Qt
|
|||
from PyQt5.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout
|
||||
from qtawesome import icon
|
||||
|
||||
from rare.components.tabs.games.game_widgets.base_installed_widget import BaseInstalledWidget
|
||||
from rare.components.tabs.games.game_widgets.base_installed_widget import (
|
||||
BaseInstalledWidget,
|
||||
)
|
||||
from rare.utils.utils import get_size
|
||||
|
||||
logger = getLogger("GameWidget")
|
||||
|
@ -36,7 +38,9 @@ class InstalledListWidget(BaseInstalledWidget):
|
|||
self.title_label.setWordWrap(True)
|
||||
self.childLayout.addWidget(self.title_label)
|
||||
self.app_name_label = QLabel(self.game.app_name)
|
||||
self.launch_button = QPushButton(play_icon, self.tr("Launch") if self.igame else self.tr("Link/Play"))
|
||||
self.launch_button = QPushButton(
|
||||
play_icon, self.tr("Launch") if self.igame else self.tr("Link/Play")
|
||||
)
|
||||
self.launch_button.setObjectName("launch_game_button")
|
||||
self.launch_button.setFixedWidth(150)
|
||||
|
||||
|
@ -57,7 +61,9 @@ class InstalledListWidget(BaseInstalledWidget):
|
|||
self.childLayout.addWidget(self.developer_label)
|
||||
if self.igame:
|
||||
self.version_label = QLabel("Version: " + str(self.igame.version))
|
||||
self.size_label = QLabel(f"{self.tr('Installed size')}: {get_size(self.size)}")
|
||||
self.size_label = QLabel(
|
||||
f"{self.tr('Installed size')}: {get_size(self.size)}"
|
||||
)
|
||||
|
||||
self.childLayout.addWidget(self.version_label)
|
||||
self.childLayout.addWidget(self.size_label)
|
||||
|
|
|
@ -3,7 +3,12 @@ from PyQt5.QtGui import QPaintEvent, QPainter, QPixmap, QPen, QFont, QColor
|
|||
from PyQt5.QtWidgets import QVBoxLayout, QLabel, QHBoxLayout, QWidget
|
||||
|
||||
from rare import shared
|
||||
from rare.utils.utils import get_pixmap, optimal_text_background, text_color_for_background, get_uninstalled_pixmap
|
||||
from rare.utils.utils import (
|
||||
get_pixmap,
|
||||
optimal_text_background,
|
||||
text_color_for_background,
|
||||
get_uninstalled_pixmap,
|
||||
)
|
||||
|
||||
|
||||
class InstallingGameWidget(QWidget):
|
||||
|
@ -53,17 +58,23 @@ class PaintWidget(QWidget):
|
|||
game = shared.core.get_game(app_name, False)
|
||||
self.color_image = get_pixmap(game.app_name)
|
||||
w = 200
|
||||
self.color_image = self.color_image.scaled(w, int(w * 4 // 3), transformMode=Qt.SmoothTransformation)
|
||||
self.color_image = self.color_image.scaled(
|
||||
w, int(w * 4 // 3), transformMode=Qt.SmoothTransformation
|
||||
)
|
||||
self.setFixedSize(self.color_image.size())
|
||||
self.bw_image = get_uninstalled_pixmap(app_name)
|
||||
self.bw_image = self.bw_image.scaled(w, int(w * 4 // 3), transformMode=Qt.SmoothTransformation)
|
||||
self.bw_image = self.bw_image.scaled(
|
||||
w, int(w * 4 // 3), transformMode=Qt.SmoothTransformation
|
||||
)
|
||||
self.progress = 0
|
||||
|
||||
pixel_list = []
|
||||
for x in range(self.color_image.width()):
|
||||
for y in range(self.color_image.height()):
|
||||
# convert pixmap to qimage, get pixel and remove alpha channel
|
||||
pixel_list.append(self.color_image.toImage().pixelColor(x, y).getRgb()[:-1])
|
||||
pixel_list.append(
|
||||
self.color_image.toImage().pixelColor(x, y).getRgb()[:-1]
|
||||
)
|
||||
|
||||
self.rgb_tuple = optimal_text_background(pixel_list)
|
||||
|
||||
|
@ -74,14 +85,21 @@ class PaintWidget(QWidget):
|
|||
|
||||
w = self.bw_image.width() * self.progress // 100
|
||||
|
||||
painter.drawPixmap(0, 0, w, self.color_image.height(),
|
||||
self.color_image.copy(QRect(0, 0, w, self.color_image.height())))
|
||||
painter.drawPixmap(
|
||||
0,
|
||||
0,
|
||||
w,
|
||||
self.color_image.height(),
|
||||
self.color_image.copy(QRect(0, 0, w, self.color_image.height())),
|
||||
)
|
||||
|
||||
# Draw Circle
|
||||
pen = QPen(QColor(*self.rgb_tuple), 3)
|
||||
painter.setPen(pen)
|
||||
painter.setBrush(QColor(*self.rgb_tuple))
|
||||
painter.drawEllipse(int(self.width() / 2) - 20, int(self.height() / 2) - 20, 40, 40)
|
||||
painter.drawEllipse(
|
||||
int(self.width() / 2) - 20, int(self.height() / 2) - 20, 40, 40
|
||||
)
|
||||
|
||||
# draw text
|
||||
painter.setPen(QColor(*text_color_for_background(self.rgb_tuple)))
|
||||
|
|
|
@ -4,13 +4,14 @@ from PyQt5.QtWidgets import QVBoxLayout, QLabel
|
|||
|
||||
from legendary.core import LegendaryCore
|
||||
from legendary.models.game import Game
|
||||
from rare.components.tabs.games.game_widgets.base_uninstalled_widget import BaseUninstalledWidget
|
||||
from rare.components.tabs.games.game_widgets.base_uninstalled_widget import (
|
||||
BaseUninstalledWidget,
|
||||
)
|
||||
|
||||
logger = getLogger("Uninstalled")
|
||||
|
||||
|
||||
class IconWidgetUninstalled(BaseUninstalledWidget):
|
||||
|
||||
def __init__(self, game: Game, core: LegendaryCore, pixmap):
|
||||
super(IconWidgetUninstalled, self).__init__(game, core, pixmap)
|
||||
self.layout = QVBoxLayout()
|
||||
|
|
|
@ -4,13 +4,14 @@ from PyQt5.QtCore import Qt
|
|||
from PyQt5.QtWidgets import QLabel, QHBoxLayout, QVBoxLayout, QPushButton
|
||||
|
||||
from legendary.core import LegendaryCore
|
||||
from rare.components.tabs.games.game_widgets.base_uninstalled_widget import BaseUninstalledWidget
|
||||
from rare.components.tabs.games.game_widgets.base_uninstalled_widget import (
|
||||
BaseUninstalledWidget,
|
||||
)
|
||||
|
||||
logger = getLogger("Game")
|
||||
|
||||
|
||||
class ListWidgetUninstalled(BaseUninstalledWidget):
|
||||
|
||||
def __init__(self, core: LegendaryCore, game, pixmap):
|
||||
super(ListWidgetUninstalled, self).__init__(game, core, pixmap)
|
||||
self.layout = QHBoxLayout()
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
from PyQt5.QtCore import QSize, QSettings, pyqtSignal
|
||||
from PyQt5.QtWidgets import QLineEdit, QLabel, QPushButton, QWidget, QHBoxLayout, QComboBox
|
||||
from PyQt5.QtWidgets import (
|
||||
QLineEdit,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QWidget,
|
||||
QHBoxLayout,
|
||||
QComboBox,
|
||||
)
|
||||
from qtawesome import icon
|
||||
|
||||
from rare.utils.extra_widgets import SelectViewWidget
|
||||
|
@ -17,15 +24,26 @@ class GameListHeadBar(QWidget):
|
|||
# self.layout.addWidget(self.installed_only)
|
||||
|
||||
self.filter = QComboBox()
|
||||
self.filter.addItems([self.tr("All"),
|
||||
self.tr("Installed only"),
|
||||
self.tr("Offline Games"),
|
||||
self.tr("32 Bit Games"),
|
||||
self.tr("Mac games"),
|
||||
self.tr("Exclude Origin")])
|
||||
self.filter.addItems(
|
||||
[
|
||||
self.tr("All"),
|
||||
self.tr("Installed only"),
|
||||
self.tr("Offline Games"),
|
||||
self.tr("32 Bit Games"),
|
||||
self.tr("Mac games"),
|
||||
self.tr("Exclude Origin"),
|
||||
]
|
||||
)
|
||||
self.layout().addWidget(self.filter)
|
||||
|
||||
self.available_filters = ["all", "installed", "offline", "32bit", "mac", "installable"]
|
||||
self.available_filters = [
|
||||
"all",
|
||||
"installed",
|
||||
"offline",
|
||||
"32bit",
|
||||
"mac",
|
||||
"installable",
|
||||
]
|
||||
|
||||
try:
|
||||
self.filter.setCurrentIndex(self.settings.value("filter", 0, int))
|
||||
|
|
|
@ -6,22 +6,21 @@ from .import_group import ImportGroup
|
|||
|
||||
|
||||
class ImportSyncTabs(SideTabWidget):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ImportSyncTabs, self).__init__(show_back=True, parent=parent)
|
||||
self.import_widget = ImportSyncWidget(
|
||||
ImportGroup(self),
|
||||
self.tr('Import Game'),
|
||||
self.tr('To import games from Epic Games Store, please enable EGL Sync.'),
|
||||
self
|
||||
self.tr("Import Game"),
|
||||
self.tr("To import games from Epic Games Store, please enable EGL Sync."),
|
||||
self,
|
||||
)
|
||||
self.addTab(self.import_widget, self.tr("Import Games"))
|
||||
|
||||
self.egl_sync_widget = ImportSyncWidget(
|
||||
EGLSyncGroup(self),
|
||||
self.tr('Sync with EGL'),
|
||||
self.tr('To import EGL games from directories, please use Import Game.'),
|
||||
self
|
||||
self.tr("Sync with EGL"),
|
||||
self.tr("To import EGL games from directories, please use Import Game."),
|
||||
self,
|
||||
)
|
||||
self.addTab(self.egl_sync_widget, self.tr("Sync with EGL"))
|
||||
# FIXME: Until it is ready
|
||||
|
@ -37,7 +36,6 @@ class ImportSyncTabs(SideTabWidget):
|
|||
|
||||
|
||||
class ImportSyncWidget(QWidget):
|
||||
|
||||
def __init__(self, widget: QWidget, title: str, info: str, parent=None):
|
||||
super(ImportSyncWidget, self).__init__(parent=parent)
|
||||
self.layout = QVBoxLayout()
|
||||
|
@ -48,5 +46,7 @@ class ImportSyncWidget(QWidget):
|
|||
self.layout.addWidget(self.group)
|
||||
self.info = QLabel(f"<h4>{info}</h4>")
|
||||
self.layout.addWidget(self.info)
|
||||
self.layout.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding))
|
||||
self.layout.addItem(
|
||||
QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
||||
)
|
||||
self.setLayout(self.layout)
|
||||
|
|
|
@ -8,7 +8,9 @@ from PyQt5.QtWidgets import QGroupBox, QListWidgetItem, QFileDialog, QMessageBox
|
|||
|
||||
import rare.shared as shared
|
||||
from rare.ui.components.tabs.games.import_sync.egl_sync_group import Ui_EGLSyncGroup
|
||||
from rare.ui.components.tabs.games.import_sync.egl_sync_list_group import Ui_EGLSyncListGroup
|
||||
from rare.ui.components.tabs.games.import_sync.egl_sync_list_group import (
|
||||
Ui_EGLSyncListGroup,
|
||||
)
|
||||
from rare.utils.extra_widgets import PathEdit
|
||||
from rare.utils.models import PathSpec
|
||||
from rare.utils.utils import WineResolver
|
||||
|
@ -17,33 +19,36 @@ logger = getLogger("EGLSync")
|
|||
|
||||
|
||||
class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(EGLSyncGroup, self).__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
self.egl_path_info.setProperty('infoLabel', 1)
|
||||
self.egl_path_info.setProperty("infoLabel", 1)
|
||||
|
||||
self.thread_pool = QThreadPool.globalInstance()
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
if platform.system() == "Windows":
|
||||
self.egl_path_edit_label.setVisible(False)
|
||||
self.egl_path_info_label.setVisible(False)
|
||||
self.egl_path_info.setVisible(False)
|
||||
else:
|
||||
self.egl_path_edit = PathEdit(
|
||||
path=shared.core.egl.programdata_path,
|
||||
ph_text=self.tr('Path to the Wine prefix where EGL is installed, or the Manifests folder'),
|
||||
ph_text=self.tr(
|
||||
"Path to the Wine prefix where EGL is installed, or the Manifests folder"
|
||||
),
|
||||
file_type=QFileDialog.DirectoryOnly,
|
||||
edit_func=self.egl_path_edit_edit_cb,
|
||||
save_func=self.egl_path_edit_save_cb,
|
||||
parent=self
|
||||
parent=self,
|
||||
)
|
||||
self.egl_path_edit.textChanged.connect(self.egl_path_changed)
|
||||
self.egl_path_edit_layout.addWidget(self.egl_path_edit)
|
||||
|
||||
if not shared.core.egl.programdata_path:
|
||||
self.egl_path_info.setText(self.tr('Updating...'))
|
||||
wine_resolver = WineResolver(PathSpec.egl_programdata, 'default', shared.core)
|
||||
self.egl_path_info.setText(self.tr("Updating..."))
|
||||
wine_resolver = WineResolver(
|
||||
PathSpec.egl_programdata, "default", shared.core
|
||||
)
|
||||
wine_resolver.signals.result_ready.connect(self.wine_resolver_cb)
|
||||
self.thread_pool.start(wine_resolver)
|
||||
else:
|
||||
|
@ -67,12 +72,18 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
|
|||
self.egl_path_info.setText(path)
|
||||
if not path:
|
||||
self.egl_path_info.setText(
|
||||
self.tr('Default Wine prefix is unset, or path does not exist. '
|
||||
'Create it or configure it in Settings -> Linux.'))
|
||||
self.tr(
|
||||
"Default Wine prefix is unset, or path does not exist. "
|
||||
"Create it or configure it in Settings -> Linux."
|
||||
)
|
||||
)
|
||||
elif not os.path.exists(path):
|
||||
self.egl_path_info.setText(
|
||||
self.tr('Default Wine prefix is set but EGL manifests path does not exist. '
|
||||
'Your configured default Wine prefix might not be where EGL is installed.'))
|
||||
self.tr(
|
||||
"Default Wine prefix is set but EGL manifests path does not exist. "
|
||||
"Your configured default Wine prefix might not be where EGL is installed."
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.egl_path_edit.setText(path)
|
||||
|
||||
|
@ -80,10 +91,18 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
|
|||
def egl_path_edit_edit_cb(path) -> Tuple[bool, str]:
|
||||
if not path:
|
||||
return True, path
|
||||
if os.path.exists(os.path.join(path, 'system.reg')) and os.path.exists(os.path.join(path, 'dosdevices/c:')):
|
||||
if os.path.exists(os.path.join(path, "system.reg")) and os.path.exists(
|
||||
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 = os.path.join(
|
||||
path,
|
||||
"dosdevices/c:",
|
||||
"ProgramData/Epic/EpicGamesLauncher/Data/Manifests",
|
||||
)
|
||||
elif not path.rstrip("/").endswith(
|
||||
"ProgramData/Epic/EpicGamesLauncher/Data/Manifests"
|
||||
):
|
||||
# lower() might or might not be needed in the check
|
||||
return False, path
|
||||
if os.path.exists(path):
|
||||
|
@ -95,11 +114,11 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
|
|||
if not path or not os.path.exists(path):
|
||||
# This is the same as "--unlink"
|
||||
shared.core.egl.programdata_path = None
|
||||
shared.core.lgd.config.remove_option('Legendary', 'egl_programdata')
|
||||
shared.core.lgd.config.remove_option('Legendary', 'egl_sync')
|
||||
shared.core.lgd.config.remove_option("Legendary", "egl_programdata")
|
||||
shared.core.lgd.config.remove_option("Legendary", "egl_sync")
|
||||
# remove EGL GUIDs from all games, DO NOT remove .egstore folders because that would fuck things up.
|
||||
for igame in shared.core.get_installed_list():
|
||||
igame.egl_guid = ''
|
||||
igame.egl_guid = ""
|
||||
shared.core.install_game(igame)
|
||||
else:
|
||||
shared.core.egl.programdata_path = path
|
||||
|
@ -119,9 +138,9 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
|
|||
if state == Qt.Unchecked:
|
||||
self.import_list.setEnabled(bool(self.import_list.items))
|
||||
self.export_list.setEnabled(bool(self.export_list.items))
|
||||
shared.core.lgd.config.remove_option('Legendary', 'egl_sync')
|
||||
shared.core.lgd.config.remove_option("Legendary", "egl_sync")
|
||||
else:
|
||||
shared.core.lgd.config.set('Legendary', 'egl_sync', str(True))
|
||||
shared.core.lgd.config.set("Legendary", "egl_sync", str(True))
|
||||
# lk: do import/export here since automatic sync was selected
|
||||
self.import_list.mark(Qt.Checked)
|
||||
self.export_list.mark(Qt.Checked)
|
||||
|
@ -134,7 +153,9 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
|
|||
|
||||
def update_lists(self):
|
||||
# self.egl_watcher.blockSignals(True)
|
||||
if have_path := bool(shared.core.egl.programdata_path) and os.path.exists(shared.core.egl.programdata_path):
|
||||
if have_path := bool(shared.core.egl.programdata_path) and os.path.exists(
|
||||
shared.core.egl.programdata_path
|
||||
):
|
||||
# NOTE: need to clear known manifests to force refresh
|
||||
shared.core.egl.manifests.clear()
|
||||
self.egl_sync_check_label.setEnabled(have_path)
|
||||
|
@ -178,7 +199,6 @@ class EGLSyncListItem(QListWidgetItem):
|
|||
|
||||
|
||||
class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup):
|
||||
|
||||
def __init__(self, export: bool, parent=None):
|
||||
super(EGLSyncListGroup, self).__init__(parent=parent)
|
||||
self.setupUi(self)
|
||||
|
@ -187,19 +207,20 @@ class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup):
|
|||
self.export = export
|
||||
|
||||
if export:
|
||||
self.setTitle(self.tr('Exportable games'))
|
||||
self.label.setText(self.tr('No games to export to EGL'))
|
||||
self.action_button.setText(self.tr('Export'))
|
||||
self.setTitle(self.tr("Exportable games"))
|
||||
self.label.setText(self.tr("No games to export to EGL"))
|
||||
self.action_button.setText(self.tr("Export"))
|
||||
self.list_func = shared.core.egl_get_exportable
|
||||
else:
|
||||
self.setTitle(self.tr('Importable games'))
|
||||
self.label.setText(self.tr('No games to import from EGL'))
|
||||
self.action_button.setText(self.tr('Import'))
|
||||
self.setTitle(self.tr("Importable games"))
|
||||
self.label.setText(self.tr("No games to import from EGL"))
|
||||
self.action_button.setText(self.tr("Import"))
|
||||
self.list_func = shared.core.egl_get_importable
|
||||
|
||||
self.list.itemDoubleClicked.connect(
|
||||
lambda item:
|
||||
item.setCheckState(Qt.Unchecked) if item.checkState() != Qt.Unchecked else item.setCheckState(Qt.Checked)
|
||||
lambda item: item.setCheckState(Qt.Unchecked)
|
||||
if item.checkState() != Qt.Unchecked
|
||||
else item.setCheckState(Qt.Checked)
|
||||
)
|
||||
self.list.itemChanged.connect(self.has_selected)
|
||||
|
||||
|
@ -244,9 +265,10 @@ class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup):
|
|||
if errors:
|
||||
QMessageBox.warning(
|
||||
self.parent(),
|
||||
self.tr('The following errors occurred while {}.').format(
|
||||
self.tr('exporting') if self.export else self.tr('importing')),
|
||||
'\n'.join(errors)
|
||||
self.tr("The following errors occurred while {}.").format(
|
||||
self.tr("exporting") if self.export else self.tr("importing")
|
||||
),
|
||||
"\n".join(errors),
|
||||
)
|
||||
|
||||
@property
|
||||
|
@ -269,7 +291,7 @@ class EGLSyncWorker(QRunnable):
|
|||
self.export_list.action()
|
||||
|
||||
|
||||
'''
|
||||
"""
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QCheckBox, QPushButton, QDialog
|
||||
|
||||
|
||||
|
@ -348,4 +370,4 @@ class EGLSyncItemWidget(QGroupBox):
|
|||
# FIXME: on update_egl_widget this is going to crash because
|
||||
# FIXME: the item is not removed from the list in the python's side
|
||||
self.deleteLater()
|
||||
'''
|
||||
"""
|
||||
|
|
|
@ -21,7 +21,9 @@ class AppNameCompleter(QCompleter):
|
|||
def __init__(self, app_names: List, parent=None):
|
||||
super(AppNameCompleter, self).__init__(parent)
|
||||
# pylint: disable=E1136
|
||||
super(AppNameCompleter, self).activated[QModelIndex].connect(self.__activated_idx)
|
||||
super(AppNameCompleter, self).activated[QModelIndex].connect(
|
||||
self.__activated_idx
|
||||
)
|
||||
|
||||
model = QStandardItemModel(len(app_names), 2)
|
||||
for idx, game in enumerate(app_names):
|
||||
|
@ -51,9 +53,7 @@ class AppNameCompleter(QCompleter):
|
|||
# lk: Getting the index from the popup and trying to use it in the completer will return invalid results
|
||||
if isinstance(idx, QModelIndex):
|
||||
self.activated.emit(
|
||||
self.popup().model().data(
|
||||
self.popup().model().index(idx.row(), 1)
|
||||
)
|
||||
self.popup().model().data(self.popup().model().index(idx.row(), 1))
|
||||
)
|
||||
# TODO: implement conversion from app_name to app_title (signal loop here)
|
||||
# if isinstance(idx_str, str):
|
||||
|
@ -67,23 +67,31 @@ class ImportGroup(QGroupBox, Ui_ImportGroup):
|
|||
self.core = shared.core
|
||||
self.app_name_list = [game.app_name for game in shared.api_results.game_list]
|
||||
self.install_dir_list = [
|
||||
game.metadata.get('customAttributes', {}).get('FolderName', {}).get('value', game.app_name)
|
||||
for game in shared.api_results.game_list if not game.is_dlc]
|
||||
game.metadata.get("customAttributes", {})
|
||||
.get("FolderName", {})
|
||||
.get("value", game.app_name)
|
||||
for game in shared.api_results.game_list
|
||||
if not game.is_dlc
|
||||
]
|
||||
|
||||
self.path_edit = PathEdit(
|
||||
self.core.get_default_install_dir(),
|
||||
QFileDialog.DirectoryOnly,
|
||||
edit_func=self.path_edit_cb,
|
||||
parent=self
|
||||
parent=self,
|
||||
)
|
||||
self.path_edit.textChanged.connect(self.path_changed)
|
||||
self.path_edit_layout.addWidget(self.path_edit)
|
||||
|
||||
self.app_name = IndicatorLineEdit(
|
||||
ph_text=self.tr("Use in case the app name was not found automatically"),
|
||||
completer=AppNameCompleter(app_names=[(i.app_name, i.app_title) for i in shared.api_results.game_list]),
|
||||
completer=AppNameCompleter(
|
||||
app_names=[
|
||||
(i.app_name, i.app_title) for i in shared.api_results.game_list
|
||||
]
|
||||
),
|
||||
edit_func=self.app_name_edit_cb,
|
||||
parent=self
|
||||
parent=self,
|
||||
)
|
||||
self.app_name.textChanged.connect(self.app_name_changed)
|
||||
self.app_name_layout.addWidget(self.app_name)
|
||||
|
@ -144,18 +152,27 @@ class ImportGroup(QGroupBox, Ui_ImportGroup):
|
|||
self.info_label.setText(self.tr("Could not find app name"))
|
||||
return
|
||||
|
||||
if not (err := legendary_utils.import_game(self.core, app_name=app_name, path=path)):
|
||||
if not (
|
||||
err := legendary_utils.import_game(self.core, app_name=app_name, path=path)
|
||||
):
|
||||
igame = self.core.get_installed_game(app_name)
|
||||
self.info_label.setText(self.tr("Successfully imported {}").format(igame.title))
|
||||
self.info_label.setText(
|
||||
self.tr("Successfully imported {}").format(igame.title)
|
||||
)
|
||||
self.app_name.setText(str())
|
||||
shared.signals.update_gamelist.emit([app_name])
|
||||
|
||||
if igame.version != self.core.get_asset(app_name, igame.platform, False).build_version:
|
||||
if (
|
||||
igame.version
|
||||
!= self.core.get_asset(app_name, igame.platform, False).build_version
|
||||
):
|
||||
# update available
|
||||
shared.signals.add_download.emit(igame.app_name)
|
||||
shared.signals.update_download_tab_text.emit()
|
||||
|
||||
else:
|
||||
logger.warning(f'Failed to import "{app_name}"')
|
||||
self.info_label.setText(self.tr("Could not import {}: ").format(app_name) + err)
|
||||
self.info_label.setText(
|
||||
self.tr("Could not import {}: ").format(app_name) + err
|
||||
)
|
||||
return
|
||||
|
|
|
@ -22,6 +22,8 @@ class SettingsTab(SideTabWidget):
|
|||
|
||||
self.about = About()
|
||||
self.addTab(self.about, "About")
|
||||
self.about.update_available_ready.connect(lambda: self.tabBar().setTabText(about_tab, "About (!)"))
|
||||
self.about.update_available_ready.connect(
|
||||
lambda: self.tabBar().setTabText(about_tab, "About (!)")
|
||||
)
|
||||
|
||||
self.setCurrentIndex(0)
|
||||
|
|
|
@ -33,10 +33,14 @@ class About(QWidget, Ui_About):
|
|||
self.open_browser.setVisible(False)
|
||||
|
||||
self.manager = QtRequestManager("json")
|
||||
self.manager.get("https://api.github.com/repos/Dummerle/Rare/releases/latest", self.update_available_finished)
|
||||
self.manager.get(
|
||||
"https://api.github.com/repos/Dummerle/Rare/releases/latest",
|
||||
self.update_available_finished,
|
||||
)
|
||||
|
||||
self.open_browser.clicked.connect(
|
||||
lambda: webbrowser.open("https://github.com/Dummerle/Rare/releases/latest"))
|
||||
lambda: webbrowser.open("https://github.com/Dummerle/Rare/releases/latest")
|
||||
)
|
||||
|
||||
def update_available_finished(self, data: dict):
|
||||
if latest_tag := data.get("tag_name"):
|
||||
|
@ -46,7 +50,9 @@ class About(QWidget, Ui_About):
|
|||
|
||||
if self.update_available:
|
||||
logger.info(f"Update available: {__version__} -> {latest_tag}")
|
||||
self.update_lbl.setText(self.tr("Update available: ") + f"{__version__} -> {latest_tag}")
|
||||
self.update_lbl.setText(
|
||||
self.tr("Update available: ") + f"{__version__} -> {latest_tag}"
|
||||
)
|
||||
self.update_label.setVisible(True)
|
||||
self.update_lbl.setVisible(True)
|
||||
self.open_browser.setVisible(True)
|
||||
|
|
|
@ -9,7 +9,6 @@ logger = getLogger("DXVK Settings")
|
|||
|
||||
|
||||
class DxvkSettings(QGroupBox, Ui_DxvkSettings):
|
||||
|
||||
def __init__(self, name=None):
|
||||
super(DxvkSettings, self).__init__()
|
||||
self.setupUi(self)
|
||||
|
@ -39,7 +38,9 @@ class DxvkSettings(QGroupBox, Ui_DxvkSettings):
|
|||
# Custom Options, index 3, adds DXVK_HUD=devinfo,fps and enables the customization panel
|
||||
|
||||
def load_settings(self):
|
||||
dxvk_options = self.core.lgd.config.get(f"{self.name}.env", "DXVK_HUD", fallback=None)
|
||||
dxvk_options = self.core.lgd.config.get(
|
||||
f"{self.name}.env", "DXVK_HUD", fallback=None
|
||||
)
|
||||
self.gb_dxvk_options.setDisabled(True)
|
||||
if dxvk_options is not None:
|
||||
if dxvk_options == "0":
|
||||
|
@ -74,16 +75,23 @@ class DxvkSettings(QGroupBox, Ui_DxvkSettings):
|
|||
dxvk_options.append(opt)
|
||||
if not dxvk_options:
|
||||
# Check if this is the first activation
|
||||
stored = self.core.lgd.config.get(f"{self.name}.env", "DXVK_HUD", fallback=None)
|
||||
stored = self.core.lgd.config.get(
|
||||
f"{self.name}.env", "DXVK_HUD", fallback=None
|
||||
)
|
||||
if stored not in [None, "0", "1"]:
|
||||
self.core.lgd.config[f"{self.name}.env"]["DXVK_HUD"] = "0"
|
||||
else:
|
||||
dxvk_options = ["devinfo", "fps"]
|
||||
# Check again if dxvk_options changed due to first activation
|
||||
if dxvk_options:
|
||||
self.core.lgd.config[f"{self.name}.env"]["DXVK_HUD"] = ",".join(dxvk_options)
|
||||
self.core.lgd.config[f"{self.name}.env"]["DXVK_HUD"] = ",".join(
|
||||
dxvk_options
|
||||
)
|
||||
else:
|
||||
if self.core.lgd.config.get(f"{self.name}.env", "DXVK_HUD", fallback=None) is not None:
|
||||
if (
|
||||
self.core.lgd.config.get(f"{self.name}.env", "DXVK_HUD", fallback=None)
|
||||
is not None
|
||||
):
|
||||
self.core.lgd.config.remove_option(f"{self.name}.env", "DXVK_HUD")
|
||||
if not self.core.lgd.config[f"{self.name}.env"]:
|
||||
self.core.lgd.config.remove_section(f"{self.name}.env")
|
||||
|
|
|
@ -21,42 +21,46 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
|
|||
self.core = shared.core
|
||||
|
||||
# Default installation directory
|
||||
self.install_dir = PathEdit(self.core.get_default_install_dir(),
|
||||
file_type=QFileDialog.DirectoryOnly,
|
||||
save_func=self.path_save)
|
||||
self.install_dir = PathEdit(
|
||||
self.core.get_default_install_dir(),
|
||||
file_type=QFileDialog.DirectoryOnly,
|
||||
save_func=self.path_save,
|
||||
)
|
||||
self.install_dir_layout.addWidget(self.install_dir)
|
||||
|
||||
# Max Workers
|
||||
max_workers = self.core.lgd.config['Legendary'].getint('max_workers', fallback=0)
|
||||
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)
|
||||
# Max memory
|
||||
max_memory = self.core.lgd.config['Legendary'].getint('max_memory', fallback=0)
|
||||
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)
|
||||
# Preferred CDN
|
||||
preferred_cdn = self.core.lgd.config['Legendary'].get('preferred_cdn', fallback="")
|
||||
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)
|
||||
# Disable HTTPS
|
||||
disable_https = self.core.lgd.config['Legendary'].getboolean('disable_https', fallback=False)
|
||||
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)
|
||||
|
||||
# Cleanup
|
||||
self.clean_button.clicked.connect(
|
||||
lambda: self.cleanup(False)
|
||||
)
|
||||
self.clean_keep_manifests_button.clicked.connect(
|
||||
lambda: self.cleanup(True)
|
||||
)
|
||||
self.clean_button.clicked.connect(lambda: self.cleanup(False))
|
||||
self.clean_keep_manifests_button.clicked.connect(lambda: self.cleanup(True))
|
||||
|
||||
self.locale_edit = IndicatorLineEdit(
|
||||
f"{self.core.language_code}-{self.core.country_code}",
|
||||
edit_func=self.locale_edit_cb,
|
||||
save_func=self.locale_save_cb,
|
||||
horiz_policy=QSizePolicy.Minimum,
|
||||
parent=self
|
||||
parent=self,
|
||||
)
|
||||
self.locale_layout.addWidget(self.locale_edit)
|
||||
|
||||
|
@ -111,28 +115,41 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
|
|||
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.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...')
|
||||
logger.debug("Removing app metadata...")
|
||||
app_names = set(g.app_name for g in self.core.get_assets(update_assets=False))
|
||||
self.core.lgd.clean_metadata(app_names)
|
||||
|
||||
if not keep_manifests:
|
||||
logger.debug('Removing manifests...')
|
||||
installed = [(ig.app_name, ig.version) for ig in self.core.get_installed_list()]
|
||||
installed.extend((ig.app_name, ig.version) for ig in self.core.get_installed_dlc_list())
|
||||
logger.debug("Removing manifests...")
|
||||
installed = [
|
||||
(ig.app_name, ig.version) for ig in self.core.get_installed_list()
|
||||
]
|
||||
installed.extend(
|
||||
(ig.app_name, ig.version) for ig in self.core.get_installed_dlc_list()
|
||||
)
|
||||
self.core.lgd.clean_manifests(installed)
|
||||
|
||||
logger.debug('Removing tmp data')
|
||||
logger.debug("Removing tmp data")
|
||||
self.core.lgd.clean_tmp_data()
|
||||
|
||||
after = self.core.lgd.get_dir_size()
|
||||
logger.info(f'Cleanup complete! Removed {(before - after) / 1024 / 1024:.02f} MiB.')
|
||||
logger.info(
|
||||
f"Cleanup complete! Removed {(before - after) / 1024 / 1024:.02f} MiB."
|
||||
)
|
||||
if (before - after) > 0:
|
||||
QMessageBox.information(self, "Cleanup", self.tr("Cleanup complete! Successfully removed {}").format(
|
||||
get_size(before - after)))
|
||||
QMessageBox.information(
|
||||
self,
|
||||
"Cleanup",
|
||||
self.tr("Cleanup complete! Successfully removed {}").format(
|
||||
get_size(before - after)
|
||||
),
|
||||
)
|
||||
else:
|
||||
QMessageBox.information(self, "Cleanup", "Nothing to clean")
|
||||
|
|
|
@ -21,7 +21,8 @@ class LinuxSettings(QWidget, Ui_LinuxSettings):
|
|||
self.wine_prefix = PathEdit(
|
||||
self.load_prefix(),
|
||||
file_type=QFileDialog.DirectoryOnly,
|
||||
save_func=self.save_prefix)
|
||||
save_func=self.save_prefix,
|
||||
)
|
||||
self.prefix_layout.addWidget(self.wine_prefix)
|
||||
|
||||
# Wine executable
|
||||
|
@ -29,7 +30,10 @@ class LinuxSettings(QWidget, Ui_LinuxSettings):
|
|||
self.load_setting(self.name, "wine_executable"),
|
||||
file_type=QFileDialog.ExistingFile,
|
||||
name_filter="Wine executable (wine wine64)",
|
||||
save_func=lambda text: self.save_setting(text, section=self.name, setting="wine_executable"))
|
||||
save_func=lambda text: self.save_setting(
|
||||
text, section=self.name, setting="wine_executable"
|
||||
),
|
||||
)
|
||||
self.exec_layout.addWidget(self.wine_exec)
|
||||
|
||||
# dxvk
|
||||
|
@ -37,13 +41,15 @@ class LinuxSettings(QWidget, Ui_LinuxSettings):
|
|||
self.dxvk_layout.addWidget(self.dxvk)
|
||||
|
||||
def load_prefix(self) -> str:
|
||||
return self.load_setting(f'{self.name}.env',
|
||||
'WINEPREFIX',
|
||||
fallback=self.load_setting(self.name, 'wine_prefix'))
|
||||
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.save_setting(text, self.name, 'wine_prefix')
|
||||
self.save_setting(text, f"{self.name}.env", "WINEPREFIX")
|
||||
self.save_setting(text, self.name, "wine_prefix")
|
||||
|
||||
@staticmethod
|
||||
def load_setting(section: str, setting: str, fallback: str = str()):
|
||||
|
@ -59,7 +65,9 @@ class LinuxSettings(QWidget, Ui_LinuxSettings):
|
|||
logger.debug(f"Set {setting} in {f'[{section}]'} to {text}")
|
||||
|
||||
else:
|
||||
if shared.core.lgd.config.has_section(section) and shared.core.lgd.config.has_option(section, setting):
|
||||
if shared.core.lgd.config.has_section(
|
||||
section
|
||||
) and shared.core.lgd.config.has_option(section, setting):
|
||||
shared.core.lgd.config.remove_option(section, setting)
|
||||
logger.debug(f"Unset {setting} from {f'[{section}]'}")
|
||||
if not shared.core.lgd.config[section]:
|
||||
|
|
|
@ -11,15 +11,17 @@ from rare import cache_dir, shared
|
|||
from rare.components.tabs.settings.rpc import RPCSettings
|
||||
from rare.ui.components.tabs.settings.rare import Ui_RareSettings
|
||||
from rare.utils import utils
|
||||
from rare.utils.utils import get_translations, get_color_schemes, set_color_pallete, get_style_sheets, set_style_sheet
|
||||
from rare.utils.utils import (
|
||||
get_translations,
|
||||
get_color_schemes,
|
||||
set_color_pallete,
|
||||
get_style_sheets,
|
||||
set_style_sheet,
|
||||
)
|
||||
|
||||
logger = getLogger("RareSettings")
|
||||
|
||||
languages = [
|
||||
("en", "English"),
|
||||
("de", "Deutsch"),
|
||||
("fr", "Français")
|
||||
]
|
||||
languages = [("en", "English"), ("de", "Deutsch"), ("fr", "Français")]
|
||||
|
||||
|
||||
class RareSettings(QWidget, Ui_RareSettings):
|
||||
|
@ -84,27 +86,35 @@ class RareSettings(QWidget, Ui_RareSettings):
|
|||
lambda: self.settings.setValue("auto_update", self.auto_update.isChecked())
|
||||
)
|
||||
self.confirm_start.stateChanged.connect(
|
||||
lambda: self.settings.setValue("confirm_start", self.confirm_start.isChecked())
|
||||
lambda: self.settings.setValue(
|
||||
"confirm_start", self.confirm_start.isChecked()
|
||||
)
|
||||
)
|
||||
self.auto_sync_cloud.stateChanged.connect(
|
||||
lambda: self.settings.setValue("auto_sync_cloud", self.auto_sync_cloud.isChecked())
|
||||
lambda: self.settings.setValue(
|
||||
"auto_sync_cloud", self.auto_sync_cloud.isChecked()
|
||||
)
|
||||
)
|
||||
self.notification.stateChanged.connect(
|
||||
lambda: self.settings.setValue("notification", self.notification.isChecked())
|
||||
)
|
||||
self.save_size.stateChanged.connect(
|
||||
self.save_window_size
|
||||
lambda: self.settings.setValue(
|
||||
"notification", self.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())
|
||||
)
|
||||
|
||||
if platform.system() == "Linux":
|
||||
self.desktop_file = os.path.expanduser("~/Desktop/Rare.desktop")
|
||||
self.start_menu_link = os.path.expanduser("~/.local/share/applications/Rare.desktop")
|
||||
self.start_menu_link = os.path.expanduser(
|
||||
"~/.local/share/applications/Rare.desktop"
|
||||
)
|
||||
elif platform.system() == "Windows":
|
||||
self.desktop_file = os.path.expanduser("~/Desktop/Rare.lnk")
|
||||
self.start_menu_link = os.path.expandvars("%appdata%\\Microsoft\\Windows\\Start Menu\\Rare.lnk")
|
||||
self.start_menu_link = os.path.expandvars(
|
||||
"%appdata%\\Microsoft\\Windows\\Start Menu\\Rare.lnk"
|
||||
)
|
||||
else:
|
||||
self.desktop_link_btn.setText(self.tr("Not supported"))
|
||||
self.desktop_link_btn.setDisabled(True)
|
||||
|
@ -151,7 +161,11 @@ class RareSettings(QWidget, Ui_RareSettings):
|
|||
self.startmenu_link_btn.setText(self.tr("Create start menu link"))
|
||||
except PermissionError as e:
|
||||
logger.error(str(e))
|
||||
QMessageBox.warning(self, "Error", "Permission error, cannot remove " + str(self.start_menu_link))
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Error",
|
||||
"Permission error, cannot remove " + str(self.start_menu_link),
|
||||
)
|
||||
|
||||
def create_desktop_link(self):
|
||||
try:
|
||||
|
@ -162,7 +176,11 @@ class RareSettings(QWidget, Ui_RareSettings):
|
|||
os.remove(self.desktop_file)
|
||||
self.desktop_link_btn.setText(self.tr("Create desktop link"))
|
||||
except PermissionError as e:
|
||||
logger.warning(self, "Error", "Permission error, cannot remove " + str(self.desktop_file))
|
||||
logger.warning(
|
||||
self,
|
||||
"Error",
|
||||
"Permission error, cannot remove " + str(self.desktop_file),
|
||||
)
|
||||
|
||||
def on_color_select_changed(self, color):
|
||||
if color:
|
||||
|
|
|
@ -27,20 +27,20 @@ class UbiGetInfoWorker(QRunnable):
|
|||
try:
|
||||
external_auths = shared.core.egs.get_external_auths()
|
||||
for ext_auth in external_auths:
|
||||
if ext_auth['type'] != 'ubisoft':
|
||||
if ext_auth["type"] != "ubisoft":
|
||||
continue
|
||||
ubi_account_id = ext_auth['externalAuthId']
|
||||
ubi_account_id = ext_auth["externalAuthId"]
|
||||
break
|
||||
else:
|
||||
self.signals.worker_finished.emit(set(), set(), "")
|
||||
return
|
||||
|
||||
uplay_keys = shared.core.egs.store_get_uplay_codes()
|
||||
key_list = uplay_keys['data']['PartnerIntegration']['accountUplayCodes']
|
||||
redeemed = {k['gameId'] for k in key_list if k['redeemedOnUplay']}
|
||||
key_list = uplay_keys["data"]["PartnerIntegration"]["accountUplayCodes"]
|
||||
redeemed = {k["gameId"] for k in key_list if k["redeemedOnUplay"]}
|
||||
|
||||
entitlements = shared.core.egs.get_user_entitlements()
|
||||
entitlements = {i['entitlementName'] for i in entitlements}
|
||||
entitlements = {i["entitlementName"] for i in entitlements}
|
||||
self.signals.worker_finished.emit(redeemed, entitlements, ubi_account_id)
|
||||
except Exception as e:
|
||||
logger.error(str(e))
|
||||
|
@ -61,7 +61,9 @@ class UbiConnectWorker(QRunnable):
|
|||
self.signals.linked.emit("")
|
||||
return
|
||||
try:
|
||||
shared.core.egs.store_claim_uplay_code(self.ubi_account_id, self.partner_link_id)
|
||||
shared.core.egs.store_claim_uplay_code(
|
||||
self.ubi_account_id, self.partner_link_id
|
||||
)
|
||||
shared.core.egs.store_redeem_uplay_codes(self.ubi_account_id)
|
||||
except Exception as e:
|
||||
self.signals.linked.emit(str(e))
|
||||
|
@ -85,14 +87,18 @@ class UbiLinkWidget(QWidget):
|
|||
self.ok_indicator.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
|
||||
self.layout().addWidget(self.ok_indicator)
|
||||
|
||||
self.link_button = QPushButton(self.tr("Redeem to Ubisoft") + ": Test" if shared.args.debug else "")
|
||||
self.link_button = QPushButton(
|
||||
self.tr("Redeem to Ubisoft") + ": Test" if shared.args.debug else ""
|
||||
)
|
||||
self.layout().addWidget(self.link_button)
|
||||
self.link_button.clicked.connect(self.activate)
|
||||
|
||||
def activate(self):
|
||||
self.link_button.setDisabled(True)
|
||||
# self.ok_indicator.setPixmap(icon("mdi.loading", color="grey").pixmap(20, 20))
|
||||
self.ok_indicator.setPixmap(icon("mdi.transit-connection-horizontal", color="grey").pixmap(20, 20))
|
||||
self.ok_indicator.setPixmap(
|
||||
icon("mdi.transit-connection-horizontal", color="grey").pixmap(20, 20)
|
||||
)
|
||||
|
||||
if shared.args.debug:
|
||||
worker = UbiConnectWorker(None, None)
|
||||
|
@ -103,11 +109,15 @@ class UbiLinkWidget(QWidget):
|
|||
|
||||
def worker_finished(self, error):
|
||||
if not error:
|
||||
self.ok_indicator.setPixmap(icon("ei.ok-circle", color="green").pixmap(QSize(20, 20)))
|
||||
self.ok_indicator.setPixmap(
|
||||
icon("ei.ok-circle", color="green").pixmap(QSize(20, 20))
|
||||
)
|
||||
self.link_button.setDisabled(True)
|
||||
self.link_button.setText(self.tr("Already activated"))
|
||||
else:
|
||||
self.ok_indicator.setPixmap(icon("fa.info-circle", color="red").pixmap(QSize(20, 20)))
|
||||
self.ok_indicator.setPixmap(
|
||||
icon("fa.info-circle", color="red").pixmap(QSize(20, 20))
|
||||
)
|
||||
self.ok_indicator.setToolTip(error)
|
||||
self.link_button.setText(self.tr("Try again"))
|
||||
self.link_button.setDisabled(False)
|
||||
|
@ -126,11 +136,20 @@ class UbiActivationHelper(QObject):
|
|||
|
||||
def show_ubi_games(self, redeemed: set, entitlements: set, ubi_account_id: str):
|
||||
if not redeemed and ubi_account_id != "error":
|
||||
logger.error('No linked ubisoft account found! Link your accounts via your browser and try again.')
|
||||
logger.error(
|
||||
"No linked ubisoft account found! Link your accounts via your browser and try again."
|
||||
)
|
||||
self.widget.layout().addWidget(
|
||||
QLabel(self.tr("Your account is not linked with Ubisoft. Please link your account first")))
|
||||
QLabel(
|
||||
self.tr(
|
||||
"Your account is not linked with Ubisoft. Please link your account first"
|
||||
)
|
||||
)
|
||||
)
|
||||
open_browser_button = QPushButton(self.tr("Open link page"))
|
||||
open_browser_button.clicked.connect(lambda: webbrowser.open("https://www.epicgames.com/id/link/ubisoft"))
|
||||
open_browser_button.clicked.connect(
|
||||
lambda: webbrowser.open("https://www.epicgames.com/id/link/ubisoft")
|
||||
)
|
||||
self.widget.layout().addWidget(open_browser_button)
|
||||
return
|
||||
elif ubi_account_id == "error":
|
||||
|
@ -141,17 +160,19 @@ class UbiActivationHelper(QObject):
|
|||
uplay_games = []
|
||||
activated = 0
|
||||
for game in games:
|
||||
for dlc_data in game.metadata.get('dlcItemList', []):
|
||||
if dlc_data['entitlementName'] not in entitlements:
|
||||
for dlc_data in game.metadata.get("dlcItemList", []):
|
||||
if dlc_data["entitlementName"] not in entitlements:
|
||||
continue
|
||||
|
||||
try:
|
||||
app_name = dlc_data['releaseInfo'][0]['appId']
|
||||
app_name = dlc_data["releaseInfo"][0]["appId"]
|
||||
except (IndexError, KeyError):
|
||||
app_name = 'unknown'
|
||||
app_name = "unknown"
|
||||
|
||||
dlc_game = Game(app_name=app_name, app_title=dlc_data['title'], metadata=dlc_data)
|
||||
if dlc_game.partner_link_type != 'ubisoft':
|
||||
dlc_game = Game(
|
||||
app_name=app_name, app_title=dlc_data["title"], metadata=dlc_data
|
||||
)
|
||||
if dlc_game.partner_link_type != "ubisoft":
|
||||
continue
|
||||
if dlc_game.partner_link_id in redeemed:
|
||||
continue
|
||||
|
@ -167,14 +188,22 @@ class UbiActivationHelper(QObject):
|
|||
if not uplay_games:
|
||||
if activated >= 1:
|
||||
self.widget.layout().addWidget(
|
||||
QLabel(self.tr("All your Ubisoft games have already been activated")))
|
||||
QLabel(
|
||||
self.tr("All your Ubisoft games have already been activated")
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.widget.layout().addWidget(QLabel(self.tr("You don't own any Ubisoft games")))
|
||||
self.widget.layout().addWidget(
|
||||
QLabel(self.tr("You don't own any Ubisoft games"))
|
||||
)
|
||||
if shared.args.debug:
|
||||
widget = UbiLinkWidget(Game(app_name="Test", app_title="This is a test game"), ubi_account_id)
|
||||
widget = UbiLinkWidget(
|
||||
Game(app_name="Test", app_title="This is a test game"),
|
||||
ubi_account_id,
|
||||
)
|
||||
self.widget.layout().addWidget(widget)
|
||||
return
|
||||
logger.info(f'Found {len(uplay_games)} game(s) to redeem')
|
||||
logger.info(f"Found {len(uplay_games)} game(s) to redeem")
|
||||
|
||||
for game in uplay_games:
|
||||
widget = UbiLinkWidget(game, ubi_account_id)
|
||||
|
|
|
@ -15,8 +15,11 @@ class Shop(QStackedWidget):
|
|||
def __init__(self, core: LegendaryCore):
|
||||
super(Shop, self).__init__()
|
||||
self.core = core
|
||||
self.api_core = ShopApiCore(self.core.egs.session.headers["Authorization"], self.core.language_code,
|
||||
self.core.country_code)
|
||||
self.api_core = ShopApiCore(
|
||||
self.core.egs.session.headers["Authorization"],
|
||||
self.core.language_code,
|
||||
self.core.country_code,
|
||||
)
|
||||
|
||||
self.shop = ShopWidget(cache_dir, core, self.api_core)
|
||||
self.wishlist_widget = Wishlist(self.api_core)
|
||||
|
@ -30,7 +33,10 @@ class Shop(QStackedWidget):
|
|||
self.search_results = SearchResults(self.api_core)
|
||||
self.addWidget(self.search_results)
|
||||
self.search_results.show_info.connect(self.show_game_info)
|
||||
self.info = ShopGameInfo([i.asset_infos["Windows"].namespace for i in shared.api_results.game_list], self.api_core)
|
||||
self.info = ShopGameInfo(
|
||||
[i.asset_infos["Windows"].namespace for i in shared.api_results.game_list],
|
||||
self.api_core,
|
||||
)
|
||||
self.addWidget(self.info)
|
||||
self.info.back_button.clicked.connect(lambda: self.setCurrentIndex(0))
|
||||
|
||||
|
|
|
@ -5,21 +5,24 @@ from PyQt5.QtCore import QObject
|
|||
class Constants(QObject):
|
||||
def __init__(self):
|
||||
super(Constants, self).__init__()
|
||||
self.categories = sorted([
|
||||
(self.tr("Action"), "1216"),
|
||||
(self.tr("Adventure"), "1117"),
|
||||
(self.tr("Puzzle"), "1298"),
|
||||
(self.tr("Open world"), "1307"),
|
||||
(self.tr("Racing"), "1212"),
|
||||
(self.tr("RPG"), "1367"),
|
||||
(self.tr("Shooter"), "1210"),
|
||||
(self.tr("Strategy"), "1115"),
|
||||
(self.tr("Survival"), "1080"),
|
||||
(self.tr("First Person"), "1294"),
|
||||
(self.tr("Indie"), "1263"),
|
||||
(self.tr("Simulation"), "1393"),
|
||||
(self.tr("Sport"), "1283")
|
||||
], key=lambda x: x[0])
|
||||
self.categories = sorted(
|
||||
[
|
||||
(self.tr("Action"), "1216"),
|
||||
(self.tr("Adventure"), "1117"),
|
||||
(self.tr("Puzzle"), "1298"),
|
||||
(self.tr("Open world"), "1307"),
|
||||
(self.tr("Racing"), "1212"),
|
||||
(self.tr("RPG"), "1367"),
|
||||
(self.tr("Shooter"), "1210"),
|
||||
(self.tr("Strategy"), "1115"),
|
||||
(self.tr("Survival"), "1080"),
|
||||
(self.tr("First Person"), "1294"),
|
||||
(self.tr("Indie"), "1263"),
|
||||
(self.tr("Simulation"), "1393"),
|
||||
(self.tr("Sport"), "1283"),
|
||||
],
|
||||
key=lambda x: x[0],
|
||||
)
|
||||
|
||||
self.platforms = [
|
||||
("MacOS", "9548"),
|
||||
|
@ -37,74 +40,78 @@ class Constants(QObject):
|
|||
(self.tr("Game"), "games/edition/base"),
|
||||
(self.tr("Bundle"), "bundles/games"),
|
||||
(self.tr("Add-on"), "addons"),
|
||||
(self.tr("Apps"), "software/edition/base")
|
||||
(self.tr("Apps"), "software/edition/base"),
|
||||
]
|
||||
|
||||
|
||||
game_query = "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, " \
|
||||
"$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " \
|
||||
"$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean " \
|
||||
"= false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, " \
|
||||
"$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n " \
|
||||
"category: $category\n count: $count\n country: $country\n keywords: $keywords\n " \
|
||||
"locale: $locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n " \
|
||||
"sortDir: $sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n " \
|
||||
"priceRange: $priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: " \
|
||||
"$effectiveDate\n ) {\n elements {\n title\n id\n namespace\n " \
|
||||
"description\n effectiveDate\n keyImages {\n type\n url\n }\n " \
|
||||
" currentPrice\n seller {\n id\n name\n }\n productSlug\n " \
|
||||
" urlSlug\n url\n tags {\n id\n }\n items {\n id\n " \
|
||||
" namespace\n }\n customAttributes {\n key\n value\n }\n " \
|
||||
"categories {\n path\n }\n catalogNs @include(if: $withMapping) {\n " \
|
||||
"mappings(pageType: \"productHome\") {\n pageSlug\n pageType\n }\n " \
|
||||
"}\n offerMappings @include(if: $withMapping) {\n pageSlug\n pageType\n " \
|
||||
"}\n price(country: $country) @include(if: $withPrice) {\n totalPrice {\n " \
|
||||
"discountPrice\n originalPrice\n voucherDiscount\n discount\n " \
|
||||
" currencyCode\n currencyInfo {\n decimals\n }\n fmtPrice(" \
|
||||
"locale: $locale) {\n originalPrice\n discountPrice\n " \
|
||||
"intermediatePrice\n }\n }\n lineOffers {\n appliedRules {\n " \
|
||||
" id\n endDate\n discountSetting {\n discountType\n " \
|
||||
" }\n }\n }\n }\n promotions(category: $category) @include(if: " \
|
||||
"$withPromotions) {\n promotionalOffers {\n promotionalOffers {\n " \
|
||||
"startDate\n endDate\n discountSetting {\n discountType\n " \
|
||||
" discountPercentage\n }\n }\n }\n " \
|
||||
"upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n " \
|
||||
"endDate\n discountSetting {\n discountType\n " \
|
||||
"discountPercentage\n }\n }\n }\n }\n }\n paging {\n " \
|
||||
" count\n total\n }\n }\n }\n}\n "
|
||||
game_query = (
|
||||
"query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, "
|
||||
"$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, "
|
||||
"$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean "
|
||||
"= false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, "
|
||||
"$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n "
|
||||
"category: $category\n count: $count\n country: $country\n keywords: $keywords\n "
|
||||
"locale: $locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n "
|
||||
"sortDir: $sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n "
|
||||
"priceRange: $priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: "
|
||||
"$effectiveDate\n ) {\n elements {\n title\n id\n namespace\n "
|
||||
"description\n effectiveDate\n keyImages {\n type\n url\n }\n "
|
||||
" currentPrice\n seller {\n id\n name\n }\n productSlug\n "
|
||||
" urlSlug\n url\n tags {\n id\n }\n items {\n id\n "
|
||||
" namespace\n }\n customAttributes {\n key\n value\n }\n "
|
||||
"categories {\n path\n }\n catalogNs @include(if: $withMapping) {\n "
|
||||
'mappings(pageType: "productHome") {\n pageSlug\n pageType\n }\n '
|
||||
"}\n offerMappings @include(if: $withMapping) {\n pageSlug\n pageType\n "
|
||||
"}\n price(country: $country) @include(if: $withPrice) {\n totalPrice {\n "
|
||||
"discountPrice\n originalPrice\n voucherDiscount\n discount\n "
|
||||
" currencyCode\n currencyInfo {\n decimals\n }\n fmtPrice("
|
||||
"locale: $locale) {\n originalPrice\n discountPrice\n "
|
||||
"intermediatePrice\n }\n }\n lineOffers {\n appliedRules {\n "
|
||||
" id\n endDate\n discountSetting {\n discountType\n "
|
||||
" }\n }\n }\n }\n promotions(category: $category) @include(if: "
|
||||
"$withPromotions) {\n promotionalOffers {\n promotionalOffers {\n "
|
||||
"startDate\n endDate\n discountSetting {\n discountType\n "
|
||||
" discountPercentage\n }\n }\n }\n "
|
||||
"upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n "
|
||||
"endDate\n discountSetting {\n discountType\n "
|
||||
"discountPercentage\n }\n }\n }\n }\n }\n paging {\n "
|
||||
" count\n total\n }\n }\n }\n}\n "
|
||||
)
|
||||
|
||||
search_query = "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, " \
|
||||
"$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " \
|
||||
"$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean = " \
|
||||
"false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, " \
|
||||
"$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n " \
|
||||
"category: $category\n count: $count\n country: $country\n keywords: $keywords\n locale: " \
|
||||
"$locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n sortDir: " \
|
||||
"$sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n priceRange: " \
|
||||
"$priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: $effectiveDate\n ) {" \
|
||||
"\n elements {\n title\n id\n namespace\n description\n " \
|
||||
"effectiveDate\n keyImages {\n type\n url\n }\n currentPrice\n " \
|
||||
"seller {\n id\n name\n }\n productSlug\n urlSlug\n url\n " \
|
||||
" tags {\n id\n }\n items {\n id\n namespace\n }\n " \
|
||||
"customAttributes {\n key\n value\n }\n categories {\n path\n " \
|
||||
"}\n catalogNs @include(if: $withMapping) {\n mappings(pageType: \"productHome\") {\n " \
|
||||
" pageSlug\n pageType\n }\n }\n offerMappings @include(if: $withMapping) " \
|
||||
"{\n pageSlug\n pageType\n }\n price(country: $country) @include(if: " \
|
||||
"$withPrice) {\n totalPrice {\n discountPrice\n originalPrice\n " \
|
||||
"voucherDiscount\n discount\n currencyCode\n currencyInfo {\n " \
|
||||
"decimals\n }\n fmtPrice(locale: $locale) {\n originalPrice\n " \
|
||||
"discountPrice\n intermediatePrice\n }\n }\n lineOffers {\n " \
|
||||
" appliedRules {\n id\n endDate\n discountSetting {\n " \
|
||||
"discountType\n }\n }\n }\n }\n promotions(category: " \
|
||||
"$category) @include(if: $withPromotions) {\n promotionalOffers {\n promotionalOffers {\n " \
|
||||
" startDate\n endDate\n discountSetting {\n " \
|
||||
"discountType\n discountPercentage\n }\n }\n }\n " \
|
||||
"upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n " \
|
||||
"endDate\n discountSetting {\n discountType\n discountPercentage\n " \
|
||||
" }\n }\n }\n }\n }\n paging {\n count\n " \
|
||||
"total\n }\n }\n }\n}\n "
|
||||
search_query = (
|
||||
"query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, "
|
||||
"$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, "
|
||||
"$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean = "
|
||||
"false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, "
|
||||
"$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n "
|
||||
"category: $category\n count: $count\n country: $country\n keywords: $keywords\n locale: "
|
||||
"$locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n sortDir: "
|
||||
"$sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n priceRange: "
|
||||
"$priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: $effectiveDate\n ) {"
|
||||
"\n elements {\n title\n id\n namespace\n description\n "
|
||||
"effectiveDate\n keyImages {\n type\n url\n }\n currentPrice\n "
|
||||
"seller {\n id\n name\n }\n productSlug\n urlSlug\n url\n "
|
||||
" tags {\n id\n }\n items {\n id\n namespace\n }\n "
|
||||
"customAttributes {\n key\n value\n }\n categories {\n path\n "
|
||||
'}\n catalogNs @include(if: $withMapping) {\n mappings(pageType: "productHome") {\n '
|
||||
" pageSlug\n pageType\n }\n }\n offerMappings @include(if: $withMapping) "
|
||||
"{\n pageSlug\n pageType\n }\n price(country: $country) @include(if: "
|
||||
"$withPrice) {\n totalPrice {\n discountPrice\n originalPrice\n "
|
||||
"voucherDiscount\n discount\n currencyCode\n currencyInfo {\n "
|
||||
"decimals\n }\n fmtPrice(locale: $locale) {\n originalPrice\n "
|
||||
"discountPrice\n intermediatePrice\n }\n }\n lineOffers {\n "
|
||||
" appliedRules {\n id\n endDate\n discountSetting {\n "
|
||||
"discountType\n }\n }\n }\n }\n promotions(category: "
|
||||
"$category) @include(if: $withPromotions) {\n promotionalOffers {\n promotionalOffers {\n "
|
||||
" startDate\n endDate\n discountSetting {\n "
|
||||
"discountType\n discountPercentage\n }\n }\n }\n "
|
||||
"upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n "
|
||||
"endDate\n discountSetting {\n discountType\n discountPercentage\n "
|
||||
" }\n }\n }\n }\n }\n paging {\n count\n "
|
||||
"total\n }\n }\n }\n}\n "
|
||||
)
|
||||
|
||||
wishlist_query = "\n query wishlistQuery($country:String!, $locale:String) {\n Wishlist {\n wishlistItems {\n elements {\n id\n order\n created\n offerId\n updated\n namespace\n \n offer {\n productSlug\n urlSlug\n title\n id\n namespace\n offerType\n expiryDate\n status\n isCodeRedemptionOnly\n description\n effectiveDate\n keyImages {\n type\n url\n }\n seller {\n id\n name\n }\n productSlug\n urlSlug\n items {\n id\n namespace\n }\n customAttributes {\n key\n value\n }\n catalogNs {\n mappings(pageType: \"productHome\") {\n pageSlug\n pageType\n }\n }\n offerMappings {\n pageSlug\n pageType\n }\n categories {\n path\n }\n price(country: $country) {\n totalPrice {\n discountPrice\n originalPrice\n voucherDiscount\n discount\n fmtPrice(locale: $locale) {\n originalPrice\n discountPrice\n intermediatePrice\n }\n currencyCode\n currencyInfo {\n decimals\n symbol\n }\n }\n lineOffers {\n appliedRules {\n id\n endDate\n }\n }\n }\n }\n\n }\n }\n }\n }\n"
|
||||
wishlist_query = '\n query wishlistQuery($country:String!, $locale:String) {\n Wishlist {\n wishlistItems {\n elements {\n id\n order\n created\n offerId\n updated\n namespace\n \n offer {\n productSlug\n urlSlug\n title\n id\n namespace\n offerType\n expiryDate\n status\n isCodeRedemptionOnly\n description\n effectiveDate\n keyImages {\n type\n url\n }\n seller {\n id\n name\n }\n productSlug\n urlSlug\n items {\n id\n namespace\n }\n customAttributes {\n key\n value\n }\n catalogNs {\n mappings(pageType: "productHome") {\n pageSlug\n pageType\n }\n }\n offerMappings {\n pageSlug\n pageType\n }\n categories {\n path\n }\n price(country: $country) {\n totalPrice {\n discountPrice\n originalPrice\n voucherDiscount\n discount\n fmtPrice(locale: $locale) {\n originalPrice\n discountPrice\n intermediatePrice\n }\n currencyCode\n currencyInfo {\n decimals\n symbol\n }\n }\n lineOffers {\n appliedRules {\n id\n endDate\n }\n }\n }\n }\n\n }\n }\n }\n }\n'
|
||||
add_to_wishlist_query = "\n mutation removeFromWishlistMutation($namespace: String!, $offerId: String!, $operation: RemoveOperation!) {\n Wishlist {\n removeFromWishlist(namespace: $namespace, offerId: $offerId, operation: $operation) {\n success\n }\n }\n }\n"
|
||||
remove_from_wishlist_query = "\n mutation removeFromWishlistMutation($namespace: String!, $offerId: String!, $operation: RemoveOperation!) {\n Wishlist {\n removeFromWishlist(namespace: $namespace, offerId: $offerId, operation: $operation) {\n success\n }\n }\n }\n"
|
||||
coupon_query = "\n query getCoupons($currencyCountry: String!, $identityId: String!, $locale: String) {\n CodeRedemption {\n coupons(currencyCountry: $currencyCountry, identityId: $identityId, includeSalesEventInfo: true) {\n code\n codeStatus\n codeType\n consumptionMetadata {\n amountDisplay {\n amount\n currency\n placement\n symbol\n }\n minSalesPriceDisplay {\n amount\n currency\n placement\n symbol\n }\n }\n endDate\n namespace\n salesEvent(locale: $locale) {\n eventName\n eventSlug\n voucherImages {\n type\n url\n }\n voucherLink\n }\n startDate\n }\n }\n }\n"
|
||||
|
|
|
@ -3,7 +3,16 @@ import webbrowser
|
|||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QPixmap, QFont
|
||||
from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QHBoxLayout, QSpacerItem, QGroupBox, QTabWidget, QGridLayout
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QHBoxLayout,
|
||||
QSpacerItem,
|
||||
QGroupBox,
|
||||
QTabWidget,
|
||||
QGridLayout,
|
||||
)
|
||||
from qtawesome import icon
|
||||
|
||||
from rare import shared
|
||||
|
@ -29,7 +38,9 @@ class ShopGameInfo(QWidget, Ui_shop_info):
|
|||
self.image_stack.addWidget(self.image)
|
||||
self.image_stack.addWidget(WaitingSpinner())
|
||||
warn_label = QLabel()
|
||||
warn_label.setPixmap(icon("fa.warning").pixmap(160, 160).scaled(240, 320, Qt.IgnoreAspectRatio))
|
||||
warn_label.setPixmap(
|
||||
icon("fa.warning").pixmap(160, 160).scaled(240, 320, Qt.IgnoreAspectRatio)
|
||||
)
|
||||
self.image_stack.addWidget(warn_label)
|
||||
|
||||
self.wishlist_button.clicked.connect(self.add_to_wishlist)
|
||||
|
@ -101,9 +112,13 @@ class ShopGameInfo(QWidget, Ui_shop_info):
|
|||
# lambda success: self.wishlist_button.setText(self.tr("Remove from wishlist"))
|
||||
# if success else self.wishlist_button.setText("Something goes wrong"))
|
||||
else:
|
||||
self.api_core.remove_from_wishlist(self.game.namespace, self.game.offer_id,
|
||||
lambda success: self.wishlist_button.setVisible(False)
|
||||
if success else self.wishlist_button.setText("Something goes wrong"))
|
||||
self.api_core.remove_from_wishlist(
|
||||
self.game.namespace,
|
||||
self.game.offer_id,
|
||||
lambda success: self.wishlist_button.setVisible(False)
|
||||
if success
|
||||
else self.wishlist_button.setText("Something goes wrong"),
|
||||
)
|
||||
|
||||
def data_received(self, game):
|
||||
try:
|
||||
|
@ -113,7 +128,12 @@ class ShopGameInfo(QWidget, Ui_shop_info):
|
|||
self.price.setText("Error")
|
||||
self.req_group_box.setVisible(False)
|
||||
for img in self.data.get("keyImages"):
|
||||
if img["type"] in ["DieselStoreFrontWide", "OfferImageTall", "VaultClosed", "ProductLogo"]:
|
||||
if img["type"] in [
|
||||
"DieselStoreFrontWide",
|
||||
"OfferImageTall",
|
||||
"VaultClosed",
|
||||
"ProductLogo",
|
||||
]:
|
||||
self.image.update_image(img["url"], size=(240, 320))
|
||||
self.image_stack.setCurrentIndex(0)
|
||||
break
|
||||
|
@ -137,7 +157,10 @@ class ShopGameInfo(QWidget, Ui_shop_info):
|
|||
font.setStrikeOut(True)
|
||||
self.price.setFont(font)
|
||||
self.discount_price.setText(
|
||||
self.game.discount_price if self.game.discount_price != "0" else self.tr("Free"))
|
||||
self.game.discount_price
|
||||
if self.game.discount_price != "0"
|
||||
else self.tr("Free")
|
||||
)
|
||||
self.discount_price.setVisible(True)
|
||||
else:
|
||||
self.discount_price.setVisible(False)
|
||||
|
@ -156,7 +179,9 @@ class ShopGameInfo(QWidget, Ui_shop_info):
|
|||
req_widget.setLayout(QGridLayout())
|
||||
req_widget.layout().addWidget(min_label, 0, 1)
|
||||
req_widget.layout().addWidget(rec_label, 0, 2)
|
||||
for i, (key, value) in enumerate(self.game.reqs.get(system, {}).items()):
|
||||
for i, (key, value) in enumerate(
|
||||
self.game.reqs.get(system, {}).items()
|
||||
):
|
||||
req_widget.layout().addWidget(QLabel(key), i + 1, 0)
|
||||
min_label = QLabel(value[0])
|
||||
min_label.setWordWrap(True)
|
||||
|
@ -167,7 +192,9 @@ class ShopGameInfo(QWidget, Ui_shop_info):
|
|||
req_tabs.addTab(req_widget, system)
|
||||
self.req_group_box.layout().addWidget(req_tabs)
|
||||
else:
|
||||
self.req_group_box.layout().addWidget(QLabel(self.tr("Could not get requirements")))
|
||||
self.req_group_box.layout().addWidget(
|
||||
QLabel(self.tr("Could not get requirements"))
|
||||
)
|
||||
self.req_group_box.setVisible(True)
|
||||
if self.game.image_urls.front_tall:
|
||||
img_url = self.game.image_urls.front_tall
|
||||
|
@ -190,7 +217,10 @@ class ShopGameInfo(QWidget, Ui_shop_info):
|
|||
self.tags.setText(", ".join(self.game.tags))
|
||||
|
||||
# clear Layout
|
||||
for widget in (self.social_link_gb.layout().itemAt(i) for i in range(self.social_link_gb.layout().count())):
|
||||
for widget in (
|
||||
self.social_link_gb.layout().itemAt(i)
|
||||
for i in range(self.social_link_gb.layout().count())
|
||||
):
|
||||
if not isinstance(widget, QSpacerItem):
|
||||
widget.widget().deleteLater()
|
||||
self.social_link_gb.deleteLater()
|
||||
|
@ -229,7 +259,10 @@ class ShopGameInfo(QWidget, Ui_shop_info):
|
|||
self.wishlist.append(game["offer"]["title"])
|
||||
|
||||
def button_clicked(self):
|
||||
webbrowser.open(f"https://www.epicgames.com/store/{shared.core.language_code}/p/" + self.slug)
|
||||
webbrowser.open(
|
||||
f"https://www.epicgames.com/store/{shared.core.language_code}/p/"
|
||||
+ self.slug
|
||||
)
|
||||
|
||||
|
||||
class SocialButton(QPushButton):
|
||||
|
|
|
@ -42,14 +42,16 @@ class GameWidget(QWidget):
|
|||
mini_layout.addWidget(self.title_label)
|
||||
mini_layout.addStretch(1)
|
||||
|
||||
price = json_info['price']['totalPrice']['fmtPrice']['originalPrice']
|
||||
discount_price = json_info['price']['totalPrice']['fmtPrice']['discountPrice']
|
||||
price = json_info["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
|
||||
discount_price = json_info["price"]["totalPrice"]["fmtPrice"]["discountPrice"]
|
||||
price_label = QLabel(price)
|
||||
if price != discount_price:
|
||||
font = QFont()
|
||||
font.setStrikeOut(True)
|
||||
price_label.setFont(font)
|
||||
mini_layout.addWidget(QLabel(discount_price if discount_price != "0" else self.tr("Free")))
|
||||
mini_layout.addWidget(
|
||||
QLabel(discount_price if discount_price != "0" else self.tr("Free"))
|
||||
)
|
||||
mini_layout.addWidget(price_label)
|
||||
else:
|
||||
if price == "0":
|
||||
|
@ -64,10 +66,19 @@ class GameWidget(QWidget):
|
|||
|
||||
self.title = json_info["title"]
|
||||
for img in json_info["keyImages"]:
|
||||
if img["type"] in ["DieselStoreFrontWide", "OfferImageWide", "VaultClosed", "ProductLogo"]:
|
||||
if img["type"] in [
|
||||
"DieselStoreFrontWide",
|
||||
"OfferImageWide",
|
||||
"VaultClosed",
|
||||
"ProductLogo",
|
||||
]:
|
||||
if img["type"] == "VaultClosed" and self.title != "Mystery Game":
|
||||
continue
|
||||
self.image.update_image(img["url"], json_info["title"], (self.width, int(self.width * 9 / 16)))
|
||||
self.image.update_image(
|
||||
img["url"],
|
||||
json_info["title"],
|
||||
(self.width, int(self.width * 9 / 16)),
|
||||
)
|
||||
break
|
||||
else:
|
||||
logger.info(", ".join([img["type"] for img in json_info["keyImages"]]))
|
||||
|
@ -95,8 +106,8 @@ class WishlistWidget(QWidget, Ui_WishlistWidget):
|
|||
break
|
||||
else:
|
||||
self.developer.setText(game["seller"]["name"])
|
||||
original_price = game['price']['totalPrice']['fmtPrice']['originalPrice']
|
||||
discount_price = game['price']['totalPrice']['fmtPrice']['discountPrice']
|
||||
original_price = game["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
|
||||
discount_price = game["price"]["totalPrice"]["fmtPrice"]["discountPrice"]
|
||||
|
||||
self.price.setText(original_price if original_price != "0" else self.tr("Free"))
|
||||
# if discount
|
||||
|
@ -118,7 +129,9 @@ class WishlistWidget(QWidget, Ui_WishlistWidget):
|
|||
url = image_model.offer_image_wide
|
||||
self.image.update_image(url, game.get("title"), (240, 135))
|
||||
self.delete_button.setIcon(icon("mdi.delete", color="white"))
|
||||
self.delete_button.clicked.connect(lambda: self.delete_from_wishlist.emit(self.game))
|
||||
self.delete_button.clicked.connect(
|
||||
lambda: self.delete_from_wishlist.emit(self.game)
|
||||
)
|
||||
|
||||
def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
|
||||
# left button
|
||||
|
|
|
@ -1,8 +1,16 @@
|
|||
from PyQt5 import QtGui
|
||||
from PyQt5.QtCore import pyqtSignal, Qt
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QScrollArea, QGroupBox, QPushButton, \
|
||||
QStackedWidget
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QScrollArea,
|
||||
QGroupBox,
|
||||
QPushButton,
|
||||
QStackedWidget,
|
||||
)
|
||||
|
||||
from rare.utils.extra_widgets import ImageLabel, FlowLayout, WaitingSpinner
|
||||
|
||||
|
@ -79,8 +87,8 @@ class _SearchResultItem(QGroupBox):
|
|||
self.title.setFont(title_font)
|
||||
self.title.setWordWrap(True)
|
||||
self.layout.addWidget(self.title)
|
||||
price = result['price']['totalPrice']['fmtPrice']['originalPrice']
|
||||
discount_price = result['price']['totalPrice']['fmtPrice']['discountPrice']
|
||||
price = result["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
|
||||
discount_price = result["price"]["totalPrice"]["fmtPrice"]["discountPrice"]
|
||||
price_layout = QHBoxLayout()
|
||||
price_label = QLabel(price if price != "0" else self.tr("Free"))
|
||||
price_layout.addWidget(price_label)
|
||||
|
|
|
@ -3,8 +3,12 @@ from logging import getLogger
|
|||
|
||||
from PyQt5.QtCore import pyqtSignal, QObject
|
||||
|
||||
from rare.components.tabs.shop.constants import wishlist_query, search_query, add_to_wishlist_query, \
|
||||
remove_from_wishlist_query
|
||||
from rare.components.tabs.shop.constants import (
|
||||
wishlist_query,
|
||||
search_query,
|
||||
add_to_wishlist_query,
|
||||
remove_from_wishlist_query,
|
||||
)
|
||||
from rare.components.tabs.shop.shop_models import BrowseModel
|
||||
from rare.utils.qt_requests import QtRequestManager
|
||||
|
||||
|
@ -46,13 +50,17 @@ class ShopApiCore(QObject):
|
|||
handle_func(results)
|
||||
|
||||
def get_wishlist(self, handle_func):
|
||||
self.auth_manager.post(graphql_url, {
|
||||
"query": wishlist_query,
|
||||
"variables": {
|
||||
"country": self.country_code,
|
||||
"locale": self.language_code + "-" + self.country_code
|
||||
}
|
||||
}, lambda data: self._handle_wishlist(data, handle_func))
|
||||
self.auth_manager.post(
|
||||
graphql_url,
|
||||
{
|
||||
"query": wishlist_query,
|
||||
"variables": {
|
||||
"country": self.country_code,
|
||||
"locale": self.language_code + "-" + self.country_code,
|
||||
},
|
||||
},
|
||||
lambda data: self._handle_wishlist(data, handle_func),
|
||||
)
|
||||
|
||||
def _handle_wishlist(self, data, handle_func):
|
||||
try:
|
||||
|
@ -71,13 +79,24 @@ class ShopApiCore(QObject):
|
|||
def search_game(self, name, handle_func):
|
||||
payload = {
|
||||
"query": search_query,
|
||||
"variables": {"category": "games/edition/base|bundles/games|editors|software/edition/base", "count": 10,
|
||||
"country": self.country_code, "keywords": name, "locale": self.locale, "sortDir": "DESC",
|
||||
"allowCountries": self.country_code,
|
||||
"start": 0, "tag": "", "withMapping": False, "withPrice": True}
|
||||
"variables": {
|
||||
"category": "games/edition/base|bundles/games|editors|software/edition/base",
|
||||
"count": 10,
|
||||
"country": self.country_code,
|
||||
"keywords": name,
|
||||
"locale": self.locale,
|
||||
"sortDir": "DESC",
|
||||
"allowCountries": self.country_code,
|
||||
"start": 0,
|
||||
"tag": "",
|
||||
"withMapping": False,
|
||||
"withPrice": True,
|
||||
},
|
||||
}
|
||||
|
||||
self.manager.post(graphql_url, payload, lambda data: self._handle_search(data, handle_func))
|
||||
self.manager.post(
|
||||
graphql_url, payload, lambda data: self._handle_search(data, handle_func)
|
||||
)
|
||||
|
||||
def _handle_search(self, data, handle_func):
|
||||
try:
|
||||
|
@ -98,13 +117,26 @@ class ShopApiCore(QObject):
|
|||
url = "https://www.epicgames.com/graphql?operationName=searchStoreQuery&variables="
|
||||
args = urllib.parse.quote_plus(str(browse_model.__dict__))
|
||||
|
||||
for old, new in [("%27", "%22"), ("+", ""), ("%3A", ":"), ("%2C", ","), ("%5B", "["), ("%5D", "]"),
|
||||
("True", "true")]:
|
||||
for old, new in [
|
||||
("%27", "%22"),
|
||||
("+", ""),
|
||||
("%3A", ":"),
|
||||
("%2C", ","),
|
||||
("%5B", "["),
|
||||
("%5D", "]"),
|
||||
("True", "true"),
|
||||
]:
|
||||
args = args.replace(old, new)
|
||||
|
||||
url = url + args + "&extensions=%7B%22persistedQuery%22:%7B%22version%22:1,%22sha256Hash%22:%220304d711e653a2914f3213a6d9163cc17153c60aef0ef52279731b02779231d2%22%7D%7D"
|
||||
url = (
|
||||
url
|
||||
+ args
|
||||
+ "&extensions=%7B%22persistedQuery%22:%7B%22version%22:1,%22sha256Hash%22:%220304d711e653a2914f3213a6d9163cc17153c60aef0ef52279731b02779231d2%22%7D%7D"
|
||||
)
|
||||
|
||||
self.auth_manager.get(url, lambda data: self._handle_browse_games(data, handle_func))
|
||||
self.auth_manager.get(
|
||||
url, lambda data: self._handle_browse_games(data, handle_func)
|
||||
)
|
||||
|
||||
def _handle_browse_games(self, data, handle_func):
|
||||
self.browse_active = False
|
||||
|
@ -143,11 +175,15 @@ class ShopApiCore(QObject):
|
|||
"offerId": offer_id,
|
||||
"namespace": namespace,
|
||||
"country": self.country_code,
|
||||
"locale": self.locale
|
||||
"locale": self.locale,
|
||||
},
|
||||
"query": add_to_wishlist_query
|
||||
"query": add_to_wishlist_query,
|
||||
}
|
||||
self.auth_manager.post(graphql_url, payload, lambda data: self._handle_add_to_wishlist(data, handle_func))
|
||||
self.auth_manager.post(
|
||||
graphql_url,
|
||||
payload,
|
||||
lambda data: self._handle_add_to_wishlist(data, handle_func),
|
||||
)
|
||||
|
||||
def _handle_add_to_wishlist(self, data, handle_func):
|
||||
try:
|
||||
|
@ -166,11 +202,15 @@ class ShopApiCore(QObject):
|
|||
"variables": {
|
||||
"offerId": offer_id,
|
||||
"namespace": namespace,
|
||||
"operation": "REMOVE"
|
||||
"operation": "REMOVE",
|
||||
},
|
||||
"query": remove_from_wishlist_query
|
||||
"query": remove_from_wishlist_query,
|
||||
}
|
||||
self.auth_manager.post(graphql_url, payload, lambda data: self._handle_remove_from_wishlist(data, handle_func))
|
||||
self.auth_manager.post(
|
||||
graphql_url,
|
||||
payload,
|
||||
lambda data: self._handle_remove_from_wishlist(data, handle_func),
|
||||
)
|
||||
|
||||
def _handle_remove_from_wishlist(self, data, handle_func):
|
||||
try:
|
||||
|
|
|
@ -3,9 +3,15 @@ from dataclasses import dataclass
|
|||
|
||||
|
||||
class ImageUrlModel:
|
||||
def __init__(self, front_tall: str = "", offer_image_tall: str = "",
|
||||
thumbnail: str = "", front_wide: str = "", offer_image_wide: str = "",
|
||||
product_logo: str = ""):
|
||||
def __init__(
|
||||
self,
|
||||
front_tall: str = "",
|
||||
offer_image_tall: str = "",
|
||||
thumbnail: str = "",
|
||||
front_wide: str = "",
|
||||
offer_image_wide: str = "",
|
||||
product_logo: str = "",
|
||||
):
|
||||
self.front_tall = front_tall
|
||||
self.offer_image_tall = offer_image_tall
|
||||
self.thumbnail = thumbnail
|
||||
|
@ -34,17 +40,30 @@ class ImageUrlModel:
|
|||
|
||||
class ShopGame:
|
||||
# TODO: Copyrights etc
|
||||
def __init__(self, title: str = "", image_urls: ImageUrlModel = None, social_links: dict = None,
|
||||
langs: list = None, reqs: dict = None, publisher: str = "", developer: str = "",
|
||||
original_price: str = "", discount_price: str = "", tags: list = None, namespace: str = "",
|
||||
offer_id: str = ""):
|
||||
def __init__(
|
||||
self,
|
||||
title: str = "",
|
||||
image_urls: ImageUrlModel = None,
|
||||
social_links: dict = None,
|
||||
langs: list = None,
|
||||
reqs: dict = None,
|
||||
publisher: str = "",
|
||||
developer: str = "",
|
||||
original_price: str = "",
|
||||
discount_price: str = "",
|
||||
tags: list = None,
|
||||
namespace: str = "",
|
||||
offer_id: str = "",
|
||||
):
|
||||
self.title = title
|
||||
self.image_urls = image_urls
|
||||
self.links = []
|
||||
if social_links:
|
||||
for item in social_links:
|
||||
if item.startswith("link"):
|
||||
self.links.append(tuple((item.replace("link", ""), social_links[item])))
|
||||
self.links.append(
|
||||
tuple((item.replace("link", ""), social_links[item]))
|
||||
)
|
||||
else:
|
||||
self.links = []
|
||||
self.languages = langs
|
||||
|
@ -77,7 +96,9 @@ class ShopGame:
|
|||
for item in links:
|
||||
if item.startswith("link"):
|
||||
tmp.links.append(tuple((item.replace("link", ""), links[item])))
|
||||
tmp.available_voice_langs = api_data["data"]["requirements"].get("languages", "Failed")
|
||||
tmp.available_voice_langs = api_data["data"]["requirements"].get(
|
||||
"languages", "Failed"
|
||||
)
|
||||
tmp.reqs = {}
|
||||
for i, system in enumerate(api_data["data"]["requirements"].get("systems", [])):
|
||||
try:
|
||||
|
@ -86,7 +107,10 @@ class ShopGame:
|
|||
continue
|
||||
for req in system["details"]:
|
||||
try:
|
||||
tmp.reqs[system["systemType"]][req["title"]] = (req["minimum"], req["recommended"])
|
||||
tmp.reqs[system["systemType"]][req["title"]] = (
|
||||
req["minimum"],
|
||||
req["recommended"],
|
||||
)
|
||||
except KeyError:
|
||||
pass
|
||||
tmp.publisher = api_data["data"]["meta"].get("publisher", "")
|
||||
|
@ -95,9 +119,14 @@ class ShopGame:
|
|||
for i in search_data["customAttributes"]:
|
||||
if i["key"] == "developerName":
|
||||
tmp.developer = i["value"]
|
||||
tmp.price = search_data['price']['totalPrice']['fmtPrice']['originalPrice']
|
||||
tmp.discount_price = search_data['price']['totalPrice']['fmtPrice']['discountPrice']
|
||||
tmp.tags = [i.replace("_", " ").capitalize() for i in api_data["data"]["meta"].get("tags", [])]
|
||||
tmp.price = search_data["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
|
||||
tmp.discount_price = search_data["price"]["totalPrice"]["fmtPrice"][
|
||||
"discountPrice"
|
||||
]
|
||||
tmp.tags = [
|
||||
i.replace("_", " ").capitalize()
|
||||
for i in api_data["data"]["meta"].get("tags", [])
|
||||
]
|
||||
tmp.namespace = search_data["namespace"]
|
||||
tmp.offer_id = search_data["id"]
|
||||
|
||||
|
@ -116,7 +145,9 @@ class BrowseModel:
|
|||
tag: str = ""
|
||||
withMapping: bool = True
|
||||
withPrice: bool = True
|
||||
date: str = f"[,{datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%dT%X')}.999Z]"
|
||||
date: str = (
|
||||
f"[,{datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%dT%X')}.999Z]"
|
||||
)
|
||||
price: str = ""
|
||||
onSale: bool = False
|
||||
|
||||
|
|
|
@ -3,7 +3,14 @@ import logging
|
|||
import random
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5.QtWidgets import QGroupBox, QScrollArea, QCheckBox, QLabel, QPushButton, QHBoxLayout
|
||||
from PyQt5.QtWidgets import (
|
||||
QGroupBox,
|
||||
QScrollArea,
|
||||
QCheckBox,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QHBoxLayout,
|
||||
)
|
||||
|
||||
from legendary.core import LegendaryCore
|
||||
from rare.ui.components.tabs.store.store import Ui_ShopWidget
|
||||
|
@ -49,7 +56,9 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
|
|||
self.game_stack.addWidget(WaitingSpinner())
|
||||
self.game_stack.setCurrentIndex(1)
|
||||
|
||||
self.search_bar = ButtonLineEdit("fa.search", placeholder_text=self.tr("Search Games"))
|
||||
self.search_bar = ButtonLineEdit(
|
||||
"fa.search", placeholder_text=self.tr("Search Games")
|
||||
)
|
||||
self.layout().insertWidget(0, self.search_bar)
|
||||
|
||||
# self.search_bar.textChanged.connect(self.search_games)
|
||||
|
@ -78,10 +87,14 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
|
|||
if item:
|
||||
item.widget().deleteLater()
|
||||
if wishlist and wishlist[0] == "error":
|
||||
self.discount_widget.layout().addWidget(QLabel(self.tr("Failed to get wishlist: ") + wishlist[1]))
|
||||
self.discount_widget.layout().addWidget(
|
||||
QLabel(self.tr("Failed to get wishlist: ") + wishlist[1])
|
||||
)
|
||||
btn = QPushButton(self.tr("Reload"))
|
||||
self.discount_widget.layout().addWidget(btn)
|
||||
btn.clicked.connect(lambda: self.api_core.get_wishlist(self.add_wishlist_items))
|
||||
btn.clicked.connect(
|
||||
lambda: self.api_core.get_wishlist(self.add_wishlist_items)
|
||||
)
|
||||
self.discount_stack.setCurrentIndex(0)
|
||||
return
|
||||
|
||||
|
@ -109,10 +122,14 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
|
|||
item.widget().deleteLater()
|
||||
|
||||
if free_games and free_games[0] == "error":
|
||||
self.free_widget.layout().addWidget(QLabel(self.tr("Failed to fetch free games: ") + free_games[1]))
|
||||
self.free_widget.layout().addWidget(
|
||||
QLabel(self.tr("Failed to fetch free games: ") + free_games[1])
|
||||
)
|
||||
btn = QPushButton(self.tr("Reload"))
|
||||
self.free_widget.layout().addWidget(btn)
|
||||
btn.clicked.connect(lambda: self.api_core.get_free_games(self.add_free_games))
|
||||
btn.clicked.connect(
|
||||
lambda: self.api_core.get_free_games(self.add_free_games)
|
||||
)
|
||||
self.free_stack.setCurrentIndex(0)
|
||||
return
|
||||
|
||||
|
@ -129,9 +146,11 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
|
|||
coming_free_games = []
|
||||
for game in free_games:
|
||||
try:
|
||||
if game['price']['totalPrice']['fmtPrice']['discountPrice'] == "0" and \
|
||||
game['price']['totalPrice']['fmtPrice']['originalPrice'] != \
|
||||
game['price']['totalPrice']['fmtPrice']['discountPrice']:
|
||||
if (
|
||||
game["price"]["totalPrice"]["fmtPrice"]["discountPrice"] == "0"
|
||||
and game["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
|
||||
!= game["price"]["totalPrice"]["fmtPrice"]["discountPrice"]
|
||||
):
|
||||
free_games_now.append(game)
|
||||
continue
|
||||
|
||||
|
@ -145,13 +164,19 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
|
|||
# parse datetime to check if game is next week or now
|
||||
try:
|
||||
start_date = datetime.datetime.strptime(
|
||||
game["promotions"]["upcomingPromotionalOffers"][0]["promotionalOffers"][0]["startDate"],
|
||||
'%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
game["promotions"]["upcomingPromotionalOffers"][0][
|
||||
"promotionalOffers"
|
||||
][0]["startDate"],
|
||||
"%Y-%m-%dT%H:%M:%S.%fZ",
|
||||
)
|
||||
except Exception:
|
||||
try:
|
||||
start_date = datetime.datetime.strptime(
|
||||
game["promotions"]["promotionalOffers"][0]["promotionalOffers"][0]["startDate"],
|
||||
'%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
game["promotions"]["promotionalOffers"][0][
|
||||
"promotionalOffers"
|
||||
][0]["startDate"],
|
||||
"%Y-%m-%dT%H:%M:%S.%fZ",
|
||||
)
|
||||
except Exception as e:
|
||||
|
||||
continue
|
||||
|
@ -171,7 +196,9 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
|
|||
self.free_game_widgets.append(w)
|
||||
now_free += 1
|
||||
if now_free == 0:
|
||||
self.free_games_now.layout().addWidget(QLabel(self.tr("Could not find current free game")))
|
||||
self.free_games_now.layout().addWidget(
|
||||
QLabel(self.tr("Could not find current free game"))
|
||||
)
|
||||
|
||||
# free games next week
|
||||
for free_game in coming_free_games:
|
||||
|
@ -187,30 +214,53 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
|
|||
|
||||
def init_filter(self):
|
||||
|
||||
self.none_price.toggled.connect(lambda: self.prepare_request("") if self.none_price.isChecked() else None)
|
||||
self.free_button.toggled.connect(lambda: self.prepare_request("free") if self.free_button.isChecked() else None)
|
||||
self.none_price.toggled.connect(
|
||||
lambda: self.prepare_request("") if self.none_price.isChecked() else None
|
||||
)
|
||||
self.free_button.toggled.connect(
|
||||
lambda: self.prepare_request("free")
|
||||
if self.free_button.isChecked()
|
||||
else None
|
||||
)
|
||||
self.under10.toggled.connect(
|
||||
lambda: self.prepare_request("<price>[0, 1000)") if self.under10.isChecked() else None)
|
||||
lambda: self.prepare_request("<price>[0, 1000)")
|
||||
if self.under10.isChecked()
|
||||
else None
|
||||
)
|
||||
self.under20.toggled.connect(
|
||||
lambda: self.prepare_request("<price>[0, 2000)") if self.under20.isChecked() else None)
|
||||
lambda: self.prepare_request("<price>[0, 2000)")
|
||||
if self.under20.isChecked()
|
||||
else None
|
||||
)
|
||||
self.under30.toggled.connect(
|
||||
lambda: self.prepare_request("<price>[0, 3000)") if self.under30.isChecked() else None)
|
||||
self.above.toggled.connect(lambda: self.prepare_request("<price>[1499,]") if self.above.isChecked() else None)
|
||||
lambda: self.prepare_request("<price>[0, 3000)")
|
||||
if self.under30.isChecked()
|
||||
else None
|
||||
)
|
||||
self.above.toggled.connect(
|
||||
lambda: self.prepare_request("<price>[1499,]")
|
||||
if self.above.isChecked()
|
||||
else None
|
||||
)
|
||||
# self.on_discount.toggled.connect(lambda: self.prepare_request("sale") if self.on_discount.isChecked() else None)
|
||||
self.on_discount.toggled.connect(lambda: self.prepare_request())
|
||||
constants = Constants()
|
||||
|
||||
self.checkboxes = []
|
||||
|
||||
for groupbox, variables in [(self.genre_gb, constants.categories),
|
||||
(self.platform_gb, constants.platforms),
|
||||
(self.others_gb, constants.others),
|
||||
(self.type_gb, constants.types)]:
|
||||
for groupbox, variables in [
|
||||
(self.genre_gb, constants.categories),
|
||||
(self.platform_gb, constants.platforms),
|
||||
(self.others_gb, constants.others),
|
||||
(self.type_gb, constants.types),
|
||||
]:
|
||||
|
||||
for text, tag in variables:
|
||||
checkbox = CheckBox(text, tag)
|
||||
checkbox.activated.connect(lambda x: self.prepare_request(added_tag=x))
|
||||
checkbox.deactivated.connect(lambda x: self.prepare_request(removed_tag=x))
|
||||
checkbox.deactivated.connect(
|
||||
lambda x: self.prepare_request(removed_tag=x)
|
||||
)
|
||||
groupbox.layout().addWidget(checkbox)
|
||||
self.checkboxes.append(checkbox)
|
||||
self.reset_button.clicked.connect(self.reset_filters)
|
||||
|
@ -228,8 +278,14 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
|
|||
|
||||
self.on_discount.setChecked(False)
|
||||
|
||||
def prepare_request(self, price: str = None, added_tag: int = 0, removed_tag: int = 0,
|
||||
added_type: str = "", removed_type: str = ""):
|
||||
def prepare_request(
|
||||
self,
|
||||
price: str = None,
|
||||
added_tag: int = 0,
|
||||
removed_tag: int = 0,
|
||||
added_type: str = "",
|
||||
removed_type: str = "",
|
||||
):
|
||||
if not self.update_games_allowed:
|
||||
return
|
||||
if price is not None:
|
||||
|
@ -255,9 +311,14 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
|
|||
self.game_stack.setCurrentIndex(1)
|
||||
date = f"[{datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d')},]"
|
||||
|
||||
browse_model = BrowseModel(language_code=self.core.language_code, country_code=self.core.country_code,
|
||||
date=date, count=20, price=self.price,
|
||||
onSale=self.on_discount.isChecked())
|
||||
browse_model = BrowseModel(
|
||||
language_code=self.core.language_code,
|
||||
country_code=self.core.country_code,
|
||||
date=date,
|
||||
count=20,
|
||||
price=self.price,
|
||||
onSale=self.on_discount.isChecked(),
|
||||
)
|
||||
browse_model.tag = "|".join(self.tags)
|
||||
|
||||
if self.types:
|
||||
|
@ -265,7 +326,10 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
|
|||
self.api_core.browse_games(browse_model, self.show_games)
|
||||
|
||||
def show_games(self, data):
|
||||
for item in (self.game_widget.layout().itemAt(i) for i in range(self.game_widget.layout().count())):
|
||||
for item in (
|
||||
self.game_widget.layout().itemAt(i)
|
||||
for i in range(self.game_widget.layout().count())
|
||||
):
|
||||
item.widget().deleteLater()
|
||||
if data:
|
||||
for game in data:
|
||||
|
@ -275,7 +339,8 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
|
|||
|
||||
else:
|
||||
self.game_widget.layout().addWidget(
|
||||
QLabel(self.tr("Could not get games matching the filter")))
|
||||
QLabel(self.tr("Could not get games matching the filter"))
|
||||
)
|
||||
self.game_stack.setCurrentIndex(0)
|
||||
|
||||
self.game_widget.layout().update()
|
||||
|
|
|
@ -21,22 +21,31 @@ class Wishlist(QStackedWidget, Ui_Wishlist):
|
|||
self.wishlist = []
|
||||
self.widgets = []
|
||||
|
||||
self.sort_cb.currentIndexChanged.connect(lambda i: self.set_wishlist(self.wishlist, i))
|
||||
self.sort_cb.currentIndexChanged.connect(
|
||||
lambda i: self.set_wishlist(self.wishlist, i)
|
||||
)
|
||||
self.filter_cb.currentIndexChanged.connect(self.set_filter)
|
||||
self.reload_button.clicked.connect(self.update_wishlist)
|
||||
self.reload_button.setIcon(icon("fa.refresh", color="white"))
|
||||
|
||||
self.reverse.stateChanged.connect(lambda: self.set_wishlist(sort=self.sort_cb.currentIndex()))
|
||||
self.reverse.stateChanged.connect(
|
||||
lambda: self.set_wishlist(sort=self.sort_cb.currentIndex())
|
||||
)
|
||||
|
||||
def update_wishlist(self):
|
||||
self.setCurrentIndex(1)
|
||||
self.api_core.get_wishlist(self.set_wishlist)
|
||||
|
||||
def delete_from_wishlist(self, game):
|
||||
self.api_core.remove_from_wishlist(game["namespace"], game["id"],
|
||||
lambda success: self.update_wishlist() if success else
|
||||
QMessageBox.warning(self, "Error",
|
||||
self.tr("Could not remove game from wishlist")))
|
||||
self.api_core.remove_from_wishlist(
|
||||
game["namespace"],
|
||||
game["id"],
|
||||
lambda success: self.update_wishlist()
|
||||
if success
|
||||
else QMessageBox.warning(
|
||||
self, "Error", self.tr("Could not remove game from wishlist")
|
||||
),
|
||||
)
|
||||
self.update_wishlist_signal.emit()
|
||||
|
||||
def set_filter(self, i):
|
||||
|
@ -69,14 +78,26 @@ class Wishlist(QStackedWidget, Ui_Wishlist):
|
|||
if sort == 0:
|
||||
sorted_list = sorted(self.wishlist, key=lambda x: x["offer"]["title"])
|
||||
elif sort == 1:
|
||||
sorted_list = sorted(self.wishlist,
|
||||
key=lambda x: x["offer"]['price']['totalPrice']['fmtPrice']['discountPrice'])
|
||||
sorted_list = sorted(
|
||||
self.wishlist,
|
||||
key=lambda x: x["offer"]["price"]["totalPrice"]["fmtPrice"][
|
||||
"discountPrice"
|
||||
],
|
||||
)
|
||||
elif sort == 2:
|
||||
sorted_list = sorted(self.wishlist, key=lambda x: x["offer"]["seller"]["name"])
|
||||
sorted_list = sorted(
|
||||
self.wishlist, key=lambda x: x["offer"]["seller"]["name"]
|
||||
)
|
||||
elif sort == 3:
|
||||
sorted_list = sorted(self.wishlist, reverse=True, key=lambda x: 1 - (
|
||||
x["offer"]["price"]["totalPrice"]["discountPrice"] / x["offer"]["price"]["totalPrice"][
|
||||
"originalPrice"]))
|
||||
sorted_list = sorted(
|
||||
self.wishlist,
|
||||
reverse=True,
|
||||
key=lambda x: 1
|
||||
- (
|
||||
x["offer"]["price"]["totalPrice"]["discountPrice"]
|
||||
/ x["offer"]["price"]["totalPrice"]["originalPrice"]
|
||||
),
|
||||
)
|
||||
else:
|
||||
sorted_list = self.wishlist
|
||||
self.widgets.clear()
|
||||
|
|
|
@ -9,7 +9,7 @@ class MainTabBar(QTabBar):
|
|||
self._expanded = expanded
|
||||
self.setObjectName("MainTabBar")
|
||||
font = self.font()
|
||||
font.setPointSize(font.pointSize()+2)
|
||||
font.setPointSize(font.pointSize() + 2)
|
||||
font.setBold(True)
|
||||
self.setFont(font)
|
||||
# self.setContentsMargins(0,10,0,10)
|
||||
|
|
|
@ -2,11 +2,36 @@ import os
|
|||
from logging import getLogger
|
||||
from typing import Callable, Tuple
|
||||
|
||||
from PyQt5.QtCore import Qt, QCoreApplication, QRect, QSize, QPoint, pyqtSignal, QFileInfo
|
||||
from PyQt5.QtCore import (
|
||||
Qt,
|
||||
QCoreApplication,
|
||||
QRect,
|
||||
QSize,
|
||||
QPoint,
|
||||
pyqtSignal,
|
||||
QFileInfo,
|
||||
)
|
||||
from PyQt5.QtGui import QMovie, QPixmap, QFontMetrics, QImage
|
||||
from PyQt5.QtWidgets import QLayout, QStyle, QSizePolicy, QLabel, QFileDialog, QHBoxLayout, QWidget, QPushButton, \
|
||||
QStyleOptionTab, QStylePainter, QTabBar, QLineEdit, QToolButton, QTabWidget, QCompleter, QFileSystemModel, \
|
||||
QStyledItemDelegate, QFileIconProvider
|
||||
from PyQt5.QtWidgets import (
|
||||
QLayout,
|
||||
QStyle,
|
||||
QSizePolicy,
|
||||
QLabel,
|
||||
QFileDialog,
|
||||
QHBoxLayout,
|
||||
QWidget,
|
||||
QPushButton,
|
||||
QStyleOptionTab,
|
||||
QStylePainter,
|
||||
QTabBar,
|
||||
QLineEdit,
|
||||
QToolButton,
|
||||
QTabWidget,
|
||||
QCompleter,
|
||||
QFileSystemModel,
|
||||
QStyledItemDelegate,
|
||||
QFileIconProvider,
|
||||
)
|
||||
from qtawesome import icon as qta_icon
|
||||
|
||||
from rare import cache_dir
|
||||
|
@ -33,15 +58,13 @@ class FlowLayout(QLayout):
|
|||
if self._hspacing >= 0:
|
||||
return self._hspacing
|
||||
else:
|
||||
return self.smartSpacing(
|
||||
QStyle.PM_LayoutHorizontalSpacing)
|
||||
return self.smartSpacing(QStyle.PM_LayoutHorizontalSpacing)
|
||||
|
||||
def verticalSpacing(self):
|
||||
if self._vspacing >= 0:
|
||||
return self._vspacing
|
||||
else:
|
||||
return self.smartSpacing(
|
||||
QStyle.PM_LayoutVerticalSpacing)
|
||||
return self.smartSpacing(QStyle.PM_LayoutVerticalSpacing)
|
||||
|
||||
def count(self):
|
||||
return len(self._items)
|
||||
|
@ -91,13 +114,13 @@ class FlowLayout(QLayout):
|
|||
hspace = self.horizontalSpacing()
|
||||
if hspace == -1:
|
||||
hspace = widget.style().layoutSpacing(
|
||||
QSizePolicy.PushButton,
|
||||
QSizePolicy.PushButton, Qt.Horizontal)
|
||||
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal
|
||||
)
|
||||
vspace = self.verticalSpacing()
|
||||
if vspace == -1:
|
||||
vspace = widget.style().layoutSpacing(
|
||||
QSizePolicy.PushButton,
|
||||
QSizePolicy.PushButton, Qt.Vertical)
|
||||
QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical
|
||||
)
|
||||
nextX = x + item.sizeHint().width() + hspace
|
||||
if nextX - hspace > effective.right() and lineheight > 0:
|
||||
x = effective.x()
|
||||
|
@ -105,8 +128,7 @@ class FlowLayout(QLayout):
|
|||
nextX = x + item.sizeHint().width() + hspace
|
||||
lineheight = 0
|
||||
if not testonly:
|
||||
item.setGeometry(
|
||||
QRect(QPoint(x, y), item.sizeHint()))
|
||||
item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
|
||||
x = nextX
|
||||
lineheight = max(lineheight, item.sizeHint().height())
|
||||
return y + lineheight - rect.y() + bottom
|
||||
|
@ -125,14 +147,16 @@ class IndicatorLineEdit(QWidget):
|
|||
textChanged = pyqtSignal(str)
|
||||
is_valid = False
|
||||
|
||||
def __init__(self,
|
||||
text: str = "",
|
||||
ph_text: str = "",
|
||||
completer: QCompleter = None,
|
||||
edit_func: Callable[[str], Tuple[bool, str]] = None,
|
||||
save_func: Callable[[str], None] = None,
|
||||
horiz_policy: QSizePolicy = QSizePolicy.Expanding,
|
||||
parent=None):
|
||||
def __init__(
|
||||
self,
|
||||
text: str = "",
|
||||
ph_text: str = "",
|
||||
completer: QCompleter = None,
|
||||
edit_func: Callable[[str], Tuple[bool, str]] = None,
|
||||
save_func: Callable[[str], None] = None,
|
||||
horiz_policy: QSizePolicy = QSizePolicy.Expanding,
|
||||
parent=None,
|
||||
):
|
||||
super(IndicatorLineEdit, self).__init__(parent=parent)
|
||||
self.setObjectName("IndicatorLineEdit")
|
||||
self.layout = QHBoxLayout(self)
|
||||
|
@ -146,7 +170,7 @@ class IndicatorLineEdit(QWidget):
|
|||
# Add hint_label to line_edit
|
||||
self.line_edit.setLayout(QHBoxLayout())
|
||||
self.hint_label = QLabel()
|
||||
self.hint_label.setObjectName('HintLabel')
|
||||
self.hint_label.setObjectName("HintLabel")
|
||||
self.hint_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
|
||||
self.line_edit.layout().setContentsMargins(0, 0, 10, 0)
|
||||
self.line_edit.layout().addWidget(self.hint_label)
|
||||
|
@ -158,13 +182,17 @@ class IndicatorLineEdit(QWidget):
|
|||
self.layout.addWidget(self.line_edit)
|
||||
if edit_func is not None:
|
||||
self.indicator_label = QLabel()
|
||||
self.indicator_label.setPixmap(qta_icon("ei.info-circle", color="gray").pixmap(16, 16))
|
||||
self.indicator_label.setPixmap(
|
||||
qta_icon("ei.info-circle", color="gray").pixmap(16, 16)
|
||||
)
|
||||
self.indicator_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
self.layout.addWidget(self.indicator_label)
|
||||
|
||||
if not ph_text:
|
||||
_translate = QCoreApplication.translate
|
||||
self.line_edit.setPlaceholderText(_translate(self.__class__.__name__, "Default"))
|
||||
self.line_edit.setPlaceholderText(
|
||||
_translate(self.__class__.__name__, "Default")
|
||||
)
|
||||
|
||||
self.edit_func = edit_func
|
||||
self.save_func = save_func
|
||||
|
@ -193,7 +221,9 @@ class IndicatorLineEdit(QWidget):
|
|||
|
||||
def __indicator(self, res):
|
||||
color = "green" if res else "red"
|
||||
self.indicator_label.setPixmap(qta_icon("ei.info-circle", color=color).pixmap(16, 16))
|
||||
self.indicator_label.setPixmap(
|
||||
qta_icon("ei.info-circle", color=color).pixmap(16, 16)
|
||||
)
|
||||
|
||||
def __edit(self, text):
|
||||
if self.edit_func is not None:
|
||||
|
@ -214,22 +244,22 @@ class IndicatorLineEdit(QWidget):
|
|||
|
||||
class PathEditIconProvider(QFileIconProvider):
|
||||
icons = [
|
||||
'mdi.file-cancel', # Unknown
|
||||
'mdi.desktop-classic', # Computer
|
||||
'mdi.desktop-mac', # Desktop
|
||||
'mdi.trash-can', # Trashcan
|
||||
'mdi.server-network', # Network
|
||||
'mdi.harddisk', # Drive
|
||||
'mdi.folder', # Folder
|
||||
'mdi.file', # File
|
||||
'mdi.cog', # Executable
|
||||
"mdi.file-cancel", # Unknown
|
||||
"mdi.desktop-classic", # Computer
|
||||
"mdi.desktop-mac", # Desktop
|
||||
"mdi.trash-can", # Trashcan
|
||||
"mdi.server-network", # Network
|
||||
"mdi.harddisk", # Drive
|
||||
"mdi.folder", # Folder
|
||||
"mdi.file", # File
|
||||
"mdi.cog", # Executable
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
super(PathEditIconProvider, self).__init__()
|
||||
self.icon_types = dict()
|
||||
for idx, icn in enumerate(self.icons):
|
||||
self.icon_types.update({idx-1: qta_icon(icn, color='#eeeeee')})
|
||||
self.icon_types.update({idx - 1: qta_icon(icn, color="#eeeeee")})
|
||||
|
||||
def icon(self, info_type):
|
||||
if isinstance(info_type, QFileInfo):
|
||||
|
@ -249,25 +279,35 @@ class PathEdit(IndicatorLineEdit):
|
|||
completer = QCompleter()
|
||||
compl_model = QFileSystemModel()
|
||||
|
||||
def __init__(self,
|
||||
path: str = "",
|
||||
file_type: QFileDialog.FileType = QFileDialog.AnyFile,
|
||||
type_filter: str = "",
|
||||
name_filter: str = "",
|
||||
ph_text: str = "",
|
||||
edit_func: Callable[[str], Tuple[bool, str]] = None,
|
||||
save_func: Callable[[str], None] = None,
|
||||
horiz_policy: QSizePolicy = QSizePolicy.Expanding,
|
||||
parent=None):
|
||||
self.compl_model.setOptions(QFileSystemModel.DontWatchForChanges |
|
||||
QFileSystemModel.DontResolveSymlinks |
|
||||
QFileSystemModel.DontUseCustomDirectoryIcons)
|
||||
def __init__(
|
||||
self,
|
||||
path: str = "",
|
||||
file_type: QFileDialog.FileType = QFileDialog.AnyFile,
|
||||
type_filter: str = "",
|
||||
name_filter: str = "",
|
||||
ph_text: str = "",
|
||||
edit_func: Callable[[str], Tuple[bool, str]] = None,
|
||||
save_func: Callable[[str], None] = None,
|
||||
horiz_policy: QSizePolicy = QSizePolicy.Expanding,
|
||||
parent=None,
|
||||
):
|
||||
self.compl_model.setOptions(
|
||||
QFileSystemModel.DontWatchForChanges
|
||||
| QFileSystemModel.DontResolveSymlinks
|
||||
| QFileSystemModel.DontUseCustomDirectoryIcons
|
||||
)
|
||||
self.compl_model.setIconProvider(PathEditIconProvider())
|
||||
self.compl_model.setRootPath(path)
|
||||
self.completer.setModel(self.compl_model)
|
||||
super(PathEdit, self).__init__(text=path, ph_text=ph_text, completer=self.completer,
|
||||
edit_func=edit_func, save_func=save_func,
|
||||
horiz_policy=horiz_policy, parent=parent)
|
||||
super(PathEdit, self).__init__(
|
||||
text=path,
|
||||
ph_text=ph_text,
|
||||
completer=self.completer,
|
||||
edit_func=edit_func,
|
||||
save_func=save_func,
|
||||
horiz_policy=horiz_policy,
|
||||
parent=parent,
|
||||
)
|
||||
self.setObjectName("PathEdit")
|
||||
self.line_edit.setMinimumSize(QSize(300, 0))
|
||||
self.path_select = QToolButton(self)
|
||||
|
@ -353,10 +393,12 @@ class SideTabWidget(QTabWidget):
|
|||
class WaitingSpinner(QLabel):
|
||||
def __init__(self):
|
||||
super(WaitingSpinner, self).__init__()
|
||||
self.setStyleSheet("""
|
||||
self.setStyleSheet(
|
||||
"""
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
""")
|
||||
"""
|
||||
)
|
||||
self.movie = QMovie(":/images/loader.gif")
|
||||
self.setMovie(self.movie)
|
||||
self.movie.start()
|
||||
|
@ -368,11 +410,15 @@ class SelectViewWidget(QWidget):
|
|||
def __init__(self, icon_view: bool):
|
||||
super(SelectViewWidget, self).__init__()
|
||||
self.icon_view = icon_view
|
||||
self.setStyleSheet("""QPushButton{border: none; background-color: transparent}""")
|
||||
self.setStyleSheet(
|
||||
"""QPushButton{border: none; background-color: transparent}"""
|
||||
)
|
||||
self.icon_view_button = QPushButton()
|
||||
self.list_view = QPushButton()
|
||||
if icon_view:
|
||||
self.icon_view_button.setIcon(qta_icon("mdi.view-grid-outline", color="orange"))
|
||||
self.icon_view_button.setIcon(
|
||||
qta_icon("mdi.view-grid-outline", color="orange")
|
||||
)
|
||||
self.list_view.setIcon(qta_icon("fa5s.list"))
|
||||
else:
|
||||
self.icon_view_button.setIcon(qta_icon("mdi.view-grid-outline"))
|
||||
|
@ -438,14 +484,19 @@ class ImageLabel(QLabel):
|
|||
return
|
||||
image = QImage()
|
||||
image.loadFromData(data)
|
||||
image = image.scaled(*self.img_size[:2], Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)
|
||||
image = image.scaled(
|
||||
*self.img_size[:2],
|
||||
Qt.KeepAspectRatio,
|
||||
transformMode=Qt.SmoothTransformation,
|
||||
)
|
||||
|
||||
pixmap = QPixmap().fromImage(image)
|
||||
self.setPixmap(pixmap)
|
||||
|
||||
def show_image(self):
|
||||
self.image = QPixmap(os.path.join(self.path, self.name)).scaled(*self.img_size,
|
||||
transformMode=Qt.SmoothTransformation)
|
||||
self.image = QPixmap(os.path.join(self.path, self.name)).scaled(
|
||||
*self.img_size, transformMode=Qt.SmoothTransformation
|
||||
)
|
||||
self.setPixmap(self.image)
|
||||
|
||||
|
||||
|
@ -457,20 +508,31 @@ class ButtonLineEdit(QLineEdit):
|
|||
|
||||
self.button = QToolButton(self)
|
||||
self.button.setIcon(qta_icon(icon_name, color="white"))
|
||||
self.button.setStyleSheet('border: 0px; padding: 0px;')
|
||||
self.button.setStyleSheet("border: 0px; padding: 0px;")
|
||||
self.button.setCursor(Qt.ArrowCursor)
|
||||
self.button.clicked.connect(self.buttonClicked.emit)
|
||||
self.setPlaceholderText(placeholder_text)
|
||||
frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
|
||||
buttonSize = self.button.sizeHint()
|
||||
|
||||
self.setStyleSheet('QLineEdit {padding-right: %dpx; }' % (buttonSize.width() + frameWidth + 1))
|
||||
self.setMinimumSize(max(self.minimumSizeHint().width(), buttonSize.width() + frameWidth * 2 + 2),
|
||||
max(self.minimumSizeHint().height(), buttonSize.height() + frameWidth * 2 + 2))
|
||||
self.setStyleSheet(
|
||||
"QLineEdit {padding-right: %dpx; }" % (buttonSize.width() + frameWidth + 1)
|
||||
)
|
||||
self.setMinimumSize(
|
||||
max(
|
||||
self.minimumSizeHint().width(), buttonSize.width() + frameWidth * 2 + 2
|
||||
),
|
||||
max(
|
||||
self.minimumSizeHint().height(),
|
||||
buttonSize.height() + frameWidth * 2 + 2,
|
||||
),
|
||||
)
|
||||
|
||||
def resizeEvent(self, event):
|
||||
buttonSize = self.button.sizeHint()
|
||||
frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
|
||||
self.button.move(self.rect().right() - frameWidth - buttonSize.width(),
|
||||
(self.rect().bottom() - buttonSize.height() + 1) // 2)
|
||||
self.button.move(
|
||||
self.rect().right() - frameWidth - buttonSize.width(),
|
||||
(self.rect().bottom() - buttonSize.height() + 1) // 2,
|
||||
)
|
||||
super(ButtonLineEdit, self).resizeEvent(event)
|
||||
|
|
|
@ -64,10 +64,7 @@ class QJsonTreeItem(object):
|
|||
return len(self._children)
|
||||
|
||||
def row(self):
|
||||
return (
|
||||
self._parent._children.index(self)
|
||||
if self._parent else 0
|
||||
)
|
||||
return self._parent._children.index(self) if self._parent else 0
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
|
@ -99,10 +96,7 @@ class QJsonTreeItem(object):
|
|||
rootItem.key = "root"
|
||||
|
||||
if isinstance(value, dict):
|
||||
items = (
|
||||
sorted(value.items())
|
||||
if sort else value.items()
|
||||
)
|
||||
items = sorted(value.items()) if sort else value.items()
|
||||
|
||||
for key, value in items:
|
||||
child = self.load(value, rootItem)
|
||||
|
@ -142,10 +136,9 @@ class QJsonModel(QtCore.QAbstractItemModel):
|
|||
|
||||
"""
|
||||
|
||||
assert isinstance(document, (dict, list, tuple)), (
|
||||
"`document` must be of dict, list or tuple, "
|
||||
"not %s" % type(document)
|
||||
)
|
||||
assert isinstance(
|
||||
document, (dict, list, tuple)
|
||||
), "`document` must be of dict, list or tuple, " "not %s" % type(document)
|
||||
|
||||
self.beginResetModel()
|
||||
|
||||
|
@ -277,7 +270,7 @@ class QJsonModel(QtCore.QAbstractItemModel):
|
|||
return item.value
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
@ -286,7 +279,8 @@ if __name__ == '__main__':
|
|||
|
||||
view.setModel(model)
|
||||
|
||||
document = json.loads("""\
|
||||
document = json.loads(
|
||||
"""\
|
||||
{
|
||||
"firstName": "John",
|
||||
"lastName": "Smith",
|
||||
|
@ -308,16 +302,16 @@ if __name__ == '__main__':
|
|||
}
|
||||
]
|
||||
}
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
model.load(document)
|
||||
model.clear()
|
||||
model.load(document)
|
||||
|
||||
# Sanity check
|
||||
assert (
|
||||
json.dumps(model.json(), sort_keys=True) ==
|
||||
json.dumps(document, sort_keys=True)
|
||||
assert json.dumps(model.json(), sort_keys=True) == json.dumps(
|
||||
document, sort_keys=True
|
||||
)
|
||||
|
||||
view.show()
|
||||
|
|
|
@ -22,15 +22,28 @@ def uninstall(app_name: str, core: LegendaryCore, options=None):
|
|||
if platform.system() == "Linux":
|
||||
if os.path.exists(os.path.expanduser(f"~/Desktop/{igame.title}.desktop")):
|
||||
os.remove(os.path.expanduser(f"~/Desktop/{igame.title}.desktop"))
|
||||
if os.path.exists(os.path.expanduser(f"~/.local/share/applications/{igame.title}.desktop")):
|
||||
os.remove(os.path.expanduser(f"~/.local/share/applications/{igame.title}.desktop"))
|
||||
if os.path.exists(
|
||||
os.path.expanduser(f"~/.local/share/applications/{igame.title}.desktop")
|
||||
):
|
||||
os.remove(
|
||||
os.path.expanduser(f"~/.local/share/applications/{igame.title}.desktop")
|
||||
)
|
||||
|
||||
elif platform.system() == "Windows":
|
||||
if os.path.exists(os.path.expanduser(f"~/Desktop/{igame.title.split(':')[0]}.lnk")):
|
||||
if os.path.exists(
|
||||
os.path.expanduser(f"~/Desktop/{igame.title.split(':')[0]}.lnk")
|
||||
):
|
||||
os.remove(os.path.expanduser(f"~/Desktop/{igame.title.split(':')[0]}.lnk"))
|
||||
elif os.path.exists(
|
||||
os.path.expandvars(f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk")):
|
||||
os.remove(os.path.expandvars(f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk"))
|
||||
os.path.expandvars(
|
||||
f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk"
|
||||
)
|
||||
):
|
||||
os.remove(
|
||||
os.path.expandvars(
|
||||
f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk"
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
# Remove DLC first so directory is empty when game uninstall runs
|
||||
|
@ -41,13 +54,17 @@ def uninstall(app_name: str, core: LegendaryCore, options=None):
|
|||
core.uninstall_game(idlc, delete_files=not options["keep_files"])
|
||||
|
||||
logger.info(f'Removing "{igame.title}" from "{igame.install_path}"...')
|
||||
core.uninstall_game(igame, delete_files=not options["keep_files"], delete_root_directory=True)
|
||||
logger.info('Game has been uninstalled.')
|
||||
core.uninstall_game(
|
||||
igame, delete_files=not options["keep_files"], delete_root_directory=True
|
||||
)
|
||||
logger.info("Game has been uninstalled.")
|
||||
if not options["keep_files"]:
|
||||
shutil.rmtree(igame.install_path)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f'Removing game failed: {e!r}, please remove {igame.install_path} manually.')
|
||||
logger.warning(
|
||||
f"Removing game failed: {e!r}, please remove {igame.install_path} manually."
|
||||
)
|
||||
|
||||
logger.info("Removing sections in config file")
|
||||
if core.lgd.config.has_section(app_name):
|
||||
|
@ -59,7 +76,7 @@ def uninstall(app_name: str, core: LegendaryCore, options=None):
|
|||
|
||||
def update_manifest(app_name: str, core: LegendaryCore):
|
||||
game = core.get_game(app_name)
|
||||
logger.info('Reloading game manifest of ' + game.app_title)
|
||||
logger.info("Reloading game manifest of " + game.app_title)
|
||||
new_manifest_data, base_urls = core.get_cdn_manifest(game)
|
||||
# overwrite base urls in metadata with current ones to avoid using old/dead CDNs
|
||||
game.base_urls = base_urls
|
||||
|
@ -67,10 +84,11 @@ def update_manifest(app_name: str, core: LegendaryCore):
|
|||
core.lgd.set_game_meta(game.app_name, game)
|
||||
|
||||
new_manifest = core.load_manifest(new_manifest_data)
|
||||
logger.debug(f'Base urls: {base_urls}')
|
||||
logger.debug(f"Base urls: {base_urls}")
|
||||
# save manifest with version name as well for testing/downgrading/etc.
|
||||
core.lgd.save_manifest(game.app_name, new_manifest_data,
|
||||
version=new_manifest.meta.build_version)
|
||||
core.lgd.save_manifest(
|
||||
game.app_name, new_manifest_data, version=new_manifest.meta.build_version
|
||||
)
|
||||
|
||||
|
||||
class VerifySignals(QObject):
|
||||
|
@ -105,8 +123,9 @@ class VerifyWorker(QRunnable):
|
|||
|
||||
manifest = self.core.load_manifest(manifest_data)
|
||||
|
||||
files = sorted(manifest.file_manifest_list.elements,
|
||||
key=lambda a: a.filename.lower())
|
||||
files = sorted(
|
||||
manifest.file_manifest_list.elements, key=lambda a: a.filename.lower()
|
||||
)
|
||||
|
||||
# build list of hashes
|
||||
file_list = [(f.filename, f.sha_hash.hex()) for f in files]
|
||||
|
@ -117,46 +136,60 @@ class VerifyWorker(QRunnable):
|
|||
|
||||
_translate = QCoreApplication.translate
|
||||
|
||||
logger.info(f'Verifying "{igame.title}" version "{manifest.meta.build_version}"')
|
||||
logger.info(
|
||||
f'Verifying "{igame.title}" version "{manifest.meta.build_version}"'
|
||||
)
|
||||
repair_file = []
|
||||
try:
|
||||
for result, path, result_hash in validate_files(igame.install_path, file_list):
|
||||
for result, path, result_hash in validate_files(
|
||||
igame.install_path, file_list
|
||||
):
|
||||
self.signals.status.emit((self.num, self.total, self.app_name))
|
||||
self.num += 1
|
||||
|
||||
if result == VerifyResult.HASH_MATCH:
|
||||
repair_file.append(f'{result_hash}:{path}')
|
||||
repair_file.append(f"{result_hash}:{path}")
|
||||
continue
|
||||
elif result == VerifyResult.HASH_MISMATCH:
|
||||
logger.error(f'File does not match hash: "{path}"')
|
||||
repair_file.append(f'{result_hash}:{path}')
|
||||
repair_file.append(f"{result_hash}:{path}")
|
||||
failed.append(path)
|
||||
elif result == VerifyResult.FILE_MISSING:
|
||||
logger.error(f'File is missing: "{path}"')
|
||||
missing.append(path)
|
||||
else:
|
||||
logger.error(f'Other failure (see log), treating file as missing: "{path}"')
|
||||
logger.error(
|
||||
f'Other failure (see log), treating file as missing: "{path}"'
|
||||
)
|
||||
missing.append(path)
|
||||
except OSError as e:
|
||||
QMessageBox.warning(None, "Error", _translate("VerifyWorker", "Path does not exist"))
|
||||
QMessageBox.warning(
|
||||
None, "Error", _translate("VerifyWorker", "Path does not exist")
|
||||
)
|
||||
logger.error(str(e))
|
||||
except ValueError as e:
|
||||
QMessageBox.warning(None, "Error", _translate("VerifyWorker", "No files to validate"))
|
||||
QMessageBox.warning(
|
||||
None, "Error", _translate("VerifyWorker", "No files to validate")
|
||||
)
|
||||
logger.error(str(e))
|
||||
|
||||
# always write repair file, even if all match
|
||||
if repair_file:
|
||||
repair_filename = os.path.join(self.core.lgd.get_tmp_path(), f'{self.app_name}.repair')
|
||||
with open(repair_filename, 'w') as f:
|
||||
f.write('\n'.join(repair_file))
|
||||
repair_filename = os.path.join(
|
||||
self.core.lgd.get_tmp_path(), f"{self.app_name}.repair"
|
||||
)
|
||||
with open(repair_filename, "w") as f:
|
||||
f.write("\n".join(repair_file))
|
||||
logger.debug(f'Written repair file to "{repair_filename}"')
|
||||
|
||||
if not missing and not failed:
|
||||
logger.info('Verification finished successfully.')
|
||||
logger.info("Verification finished successfully.")
|
||||
self.signals.summary.emit(0, 0, self.app_name)
|
||||
|
||||
else:
|
||||
logger.error(f'Verification finished, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing.')
|
||||
logger.error(
|
||||
f"Verification finished, {len(failed)} file(s) corrupted, {len(missing)} file(s) are missing."
|
||||
)
|
||||
self.signals.summary.emit(len(failed), len(missing), self.app_name)
|
||||
|
||||
|
||||
|
@ -176,30 +209,37 @@ def import_game(core: LegendaryCore, app_name: str, path: str) -> str:
|
|||
return _tr("LgdUtils", "Path does not exist")
|
||||
|
||||
manifest, igame = core.import_game(game, path)
|
||||
exe_path = os.path.join(path, manifest.meta.launch_exe.lstrip('/'))
|
||||
exe_path = os.path.join(path, manifest.meta.launch_exe.lstrip("/"))
|
||||
|
||||
if not os.path.exists(exe_path):
|
||||
logger.error(f"Launch Executable of {game.app_title} does not exist")
|
||||
return _tr("LgdUtils", "Launch executable of {} does not exist").format(game.app_title)
|
||||
return _tr("LgdUtils", "Launch executable of {} does not exist").format(
|
||||
game.app_title
|
||||
)
|
||||
|
||||
if game.is_dlc:
|
||||
release_info = game.metadata.get('mainGameItem', {}).get('releaseInfo')
|
||||
release_info = game.metadata.get("mainGameItem", {}).get("releaseInfo")
|
||||
if release_info:
|
||||
main_game_appname = release_info[0]['appId']
|
||||
main_game_title = game.metadata['mainGameItem']['title']
|
||||
main_game_appname = release_info[0]["appId"]
|
||||
main_game_title = game.metadata["mainGameItem"]["title"]
|
||||
if not core.is_installed(main_game_appname):
|
||||
return _tr("LgdUtils", "Game is a DLC, but {} is not installed").format(main_game_title)
|
||||
return _tr("LgdUtils", "Game is a DLC, but {} is not installed").format(
|
||||
main_game_title
|
||||
)
|
||||
else:
|
||||
return _tr("LgdUtils", "Unable to get base game information for DLC")
|
||||
|
||||
total = len(manifest.file_manifest_list.elements)
|
||||
found = sum(os.path.exists(os.path.join(path, f.filename))
|
||||
for f in manifest.file_manifest_list.elements)
|
||||
found = sum(
|
||||
os.path.exists(os.path.join(path, f.filename))
|
||||
for f in manifest.file_manifest_list.elements
|
||||
)
|
||||
ratio = found / total
|
||||
|
||||
if ratio < 0.9:
|
||||
logger.warning(
|
||||
"Game files are missing. It may be not the latest version or it is corrupt")
|
||||
"Game files are missing. It may be not the latest version or it is corrupt"
|
||||
)
|
||||
# return False
|
||||
core.install_game(igame)
|
||||
if igame.needs_verification:
|
||||
|
|
|
@ -20,7 +20,7 @@ class InstallOptionsModel:
|
|||
no_install: bool = False
|
||||
ignore_space_req: bool = False
|
||||
force: bool = False
|
||||
sdl_list: list = field(default_factory=lambda: [''])
|
||||
sdl_list: list = field(default_factory=lambda: [""])
|
||||
update: bool = False
|
||||
silent: bool = False
|
||||
platform: str = ""
|
||||
|
@ -47,32 +47,38 @@ class InstallQueueItemModel:
|
|||
options: InstallOptionsModel = None
|
||||
|
||||
def __bool__(self):
|
||||
return (self.status_q is not None) and (self.download is not None) and (self.options is not None)
|
||||
return (
|
||||
(self.status_q is not None)
|
||||
and (self.download is not None)
|
||||
and (self.options is not None)
|
||||
)
|
||||
|
||||
|
||||
class PathSpec:
|
||||
__egl_path_vars = {
|
||||
'{appdata}': os.path.expandvars('%LOCALAPPDATA%'),
|
||||
'{userdir}': os.path.expandvars('%USERPROFILE%/Documents'),
|
||||
"{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'),
|
||||
"{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'
|
||||
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'):
|
||||
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.__egl_path_vars.update({"{epicid}": core.lgd.userdata["account_id"]})
|
||||
self.app_name = app_name
|
||||
|
||||
def cook(self, path: str) -> str:
|
||||
cooked_path = [self.__egl_path_vars.get(p.lower(), p) for p in path.split('/')]
|
||||
cooked_path = [self.__egl_path_vars.get(p.lower(), p) for p in path.split("/")]
|
||||
return os.path.join(*cooked_path)
|
||||
|
||||
@property
|
||||
def wine_egl_programdata(self):
|
||||
return self.egl_programdata.replace('\\', '/').replace('%PROGRAMDATA%', self.wine_programdata)
|
||||
return self.egl_programdata.replace("\\", "/").replace(
|
||||
"%PROGRAMDATA%", self.wine_programdata
|
||||
)
|
||||
|
||||
def wine_egl_prefixes(self, results: int = 0) -> Union[List[str], str]:
|
||||
possible_prefixes = [
|
||||
|
@ -103,12 +109,14 @@ class ApiResults:
|
|||
saves: list = None
|
||||
|
||||
def __bool__(self):
|
||||
return self.game_list is not None \
|
||||
and self.dlcs is not None \
|
||||
and self.bit32_games is not None \
|
||||
and self.mac_games is not None \
|
||||
and self.no_asset_games is not None \
|
||||
and self.saves is not None
|
||||
return (
|
||||
self.game_list is not None
|
||||
and self.dlcs is not None
|
||||
and self.bit32_games is not None
|
||||
and self.mac_games is not None
|
||||
and self.no_asset_games is not None
|
||||
and self.saves is not None
|
||||
)
|
||||
|
||||
|
||||
class Signals(QObject):
|
||||
|
|
|
@ -29,30 +29,41 @@ class QtRequestManager(QObject):
|
|||
self.request_active = RequestQueueItem(handle_func=handle_func)
|
||||
payload = json.dumps(payload).encode("utf-8")
|
||||
|
||||
request.setHeader(QNetworkRequest.UserAgentHeader,
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36")
|
||||
request.setHeader(
|
||||
QNetworkRequest.UserAgentHeader,
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36",
|
||||
)
|
||||
|
||||
if self.authorization_token is not None:
|
||||
request.setRawHeader(b"Authorization", self.authorization_token.encode())
|
||||
request.setRawHeader(
|
||||
b"Authorization", self.authorization_token.encode()
|
||||
)
|
||||
|
||||
self.request = self.manager.post(request, payload)
|
||||
self.request.finished.connect(self.prepare_data)
|
||||
|
||||
else:
|
||||
self.request_queue.append(
|
||||
RequestQueueItem(method="post", url=url, payload=payload, handle_func=handle_func))
|
||||
RequestQueueItem(
|
||||
method="post", url=url, payload=payload, handle_func=handle_func
|
||||
)
|
||||
)
|
||||
|
||||
def get(self, url: str, handle_func: Callable[[Union[dict, bytes]], None]):
|
||||
if not self.request_active:
|
||||
request = QNetworkRequest(QUrl(url))
|
||||
request.setHeader(QNetworkRequest.UserAgentHeader,
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36")
|
||||
request.setHeader(
|
||||
QNetworkRequest.UserAgentHeader,
|
||||
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36",
|
||||
)
|
||||
|
||||
self.request_active = RequestQueueItem(handle_func=handle_func)
|
||||
self.request = self.manager.get(request)
|
||||
self.request.finished.connect(self.prepare_data)
|
||||
else:
|
||||
self.request_queue.append(RequestQueueItem(method="get", url=url, handle_func=handle_func))
|
||||
self.request_queue.append(
|
||||
RequestQueueItem(method="get", url=url, handle_func=handle_func)
|
||||
)
|
||||
|
||||
def prepare_data(self):
|
||||
# self.request_active = False
|
||||
|
@ -62,7 +73,9 @@ class QtRequestManager(QObject):
|
|||
if self.request.error() == QNetworkReply.NoError:
|
||||
if self.type == "json":
|
||||
error = QJsonParseError()
|
||||
json_data = QJsonDocument.fromJson(self.request.readAll().data(), error)
|
||||
json_data = QJsonDocument.fromJson(
|
||||
self.request.readAll().data(), error
|
||||
)
|
||||
if QJsonParseError.NoError == error.error:
|
||||
data = json.loads(json_data.toJson().data().decode())
|
||||
else:
|
||||
|
@ -78,7 +91,11 @@ class QtRequestManager(QObject):
|
|||
|
||||
if self.request_queue:
|
||||
if self.request_queue[0].method == "post":
|
||||
self.post(self.request_queue[0].url, self.request_queue[0].payload, self.request_queue[0].handle_func)
|
||||
self.post(
|
||||
self.request_queue[0].url,
|
||||
self.request_queue[0].payload,
|
||||
self.request_queue[0].handle_func,
|
||||
)
|
||||
else:
|
||||
self.get(self.request_queue[0].url, self.request_queue[0].handle_func)
|
||||
self.request_queue.pop(0)
|
||||
|
|
|
@ -61,7 +61,9 @@ class DiscordRPC(QObject):
|
|||
def set_discord_rpc(self, app_name=None):
|
||||
if not self.RPC:
|
||||
try:
|
||||
self.RPC = Presence(client_id) # Rare app: https://discord.com/developers/applications
|
||||
self.RPC = Presence(
|
||||
client_id
|
||||
) # Rare app: https://discord.com/developers/applications
|
||||
self.RPC.connect()
|
||||
except ConnectionRefusedError as e:
|
||||
logger.warning("Discord is not active\n" + str(e))
|
||||
|
@ -83,13 +85,15 @@ class DiscordRPC(QObject):
|
|||
|
||||
def update_rpc(self, app_name=None):
|
||||
if self.settings.value("rpc_enable", 0, int) == 2 or (
|
||||
app_name is None and self.settings.value("rpc_enable", 0, int) == 0):
|
||||
app_name is None and self.settings.value("rpc_enable", 0, int) == 0
|
||||
):
|
||||
self.remove_rpc()
|
||||
return
|
||||
title = None
|
||||
if not app_name:
|
||||
self.RPC.update(large_image="logo",
|
||||
details="https://github.com/Dummerle/Rare")
|
||||
self.RPC.update(
|
||||
large_image="logo", details="https://github.com/Dummerle/Rare"
|
||||
)
|
||||
return
|
||||
if self.settings.value("rpc_name", True, bool):
|
||||
try:
|
||||
|
@ -104,9 +108,7 @@ class DiscordRPC(QObject):
|
|||
if self.settings.value("rpc_os", True, bool):
|
||||
os = "via Rare on " + platform.system()
|
||||
|
||||
self.RPC.update(large_image="logo",
|
||||
details=title,
|
||||
large_text=title,
|
||||
state=os,
|
||||
start=start)
|
||||
self.RPC.update(
|
||||
large_image="logo", details=title, large_text=title, state=os, start=start
|
||||
)
|
||||
self.state = 0
|
||||
|
|
|
@ -34,36 +34,38 @@ class SingleInstance(object):
|
|||
if lockfile:
|
||||
self.lockfile = lockfile
|
||||
else:
|
||||
basename = os.path.splitext(os.path.abspath(sys.argv[0]))[0].replace(
|
||||
"/", "-").replace(":", "").replace("\\", "-") + '-%s' % flavor_id + '.lock'
|
||||
self.lockfile = os.path.normpath(
|
||||
tempfile.gettempdir() + '/' + basename)
|
||||
basename = (
|
||||
os.path.splitext(os.path.abspath(sys.argv[0]))[0]
|
||||
.replace("/", "-")
|
||||
.replace(":", "")
|
||||
.replace("\\", "-")
|
||||
+ "-%s" % flavor_id
|
||||
+ ".lock"
|
||||
)
|
||||
self.lockfile = os.path.normpath(tempfile.gettempdir() + "/" + basename)
|
||||
|
||||
logger.debug("SingleInstance lockfile: " + self.lockfile)
|
||||
if sys.platform == 'win32':
|
||||
if sys.platform == "win32":
|
||||
try:
|
||||
# file already exists, we try to remove (in case previous
|
||||
# execution was interrupted)
|
||||
if os.path.exists(self.lockfile):
|
||||
os.unlink(self.lockfile)
|
||||
self.fd = os.open(
|
||||
self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
||||
self.fd = os.open(self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
||||
except OSError:
|
||||
type, e, tb = sys.exc_info()
|
||||
if e.errno == 13:
|
||||
logger.error(
|
||||
"Another instance is already running, quitting.")
|
||||
logger.error("Another instance is already running, quitting.")
|
||||
raise SingleInstanceException()
|
||||
print(e.errno)
|
||||
raise
|
||||
else: # non Windows
|
||||
self.fp = open(self.lockfile, 'w')
|
||||
self.fp = open(self.lockfile, "w")
|
||||
self.fp.flush()
|
||||
try:
|
||||
fcntl.lockf(self.fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except IOError:
|
||||
logger.warning(
|
||||
"Another instance is already running, quitting.")
|
||||
logger.warning("Another instance is already running, quitting.")
|
||||
raise SingleInstanceException()
|
||||
self.initialized = True
|
||||
|
||||
|
@ -71,8 +73,8 @@ class SingleInstance(object):
|
|||
if not self.initialized:
|
||||
return
|
||||
try:
|
||||
if sys.platform == 'win32':
|
||||
if hasattr(self, 'fd'):
|
||||
if sys.platform == "win32":
|
||||
if hasattr(self, "fd"):
|
||||
os.close(self.fd)
|
||||
os.unlink(self.lockfile)
|
||||
else:
|
||||
|
|
|
@ -34,14 +34,16 @@ class SteamWorker(QRunnable):
|
|||
"bronze": _tr("SteamWorker", "Bronze"),
|
||||
"fail": _tr("SteamWorker", "Could not get grade"),
|
||||
"borked": _tr("SteamWorker", "unplayable"),
|
||||
"pending": _tr("SteamWorker", "Could not get grade")
|
||||
"pending": _tr("SteamWorker", "Could not get grade"),
|
||||
}
|
||||
|
||||
def set_app_name(self, app_name: str):
|
||||
self.app_name = app_name
|
||||
|
||||
def run(self) -> None:
|
||||
self.signals.rating_signal.emit(self.ratings.get(get_rating(self.app_name), self.ratings["fail"]))
|
||||
self.signals.rating_signal.emit(
|
||||
self.ratings.get(get_rating(self.app_name), self.ratings["fail"])
|
||||
)
|
||||
|
||||
|
||||
def get_rating(app_name: str):
|
||||
|
@ -57,10 +59,7 @@ def get_rating(app_name: str):
|
|||
|
||||
steam_id = get_steam_id(game.app_title)
|
||||
grade = get_grade(steam_id)
|
||||
grades[app_name] = {
|
||||
"steam_id": steam_id,
|
||||
"grade": grade
|
||||
}
|
||||
grades[app_name] = {"steam_id": steam_id, "grade": grade}
|
||||
with open(os.path.join(data_dir, "steam_ids.json"), "w") as f:
|
||||
f.write(json.dumps(grades))
|
||||
f.close()
|
||||
|
@ -74,14 +73,14 @@ def get_grade(steam_code):
|
|||
if steam_code == 0:
|
||||
return "fail"
|
||||
steam_code = str(steam_code)
|
||||
url = 'https://www.protondb.com/api/v1/reports/summaries/'
|
||||
res = requests.get(url + steam_code + '.json')
|
||||
url = "https://www.protondb.com/api/v1/reports/summaries/"
|
||||
res = requests.get(url + steam_code + ".json")
|
||||
try:
|
||||
lista = json.loads(res.text)
|
||||
except json.decoder.JSONDecodeError:
|
||||
return "fail"
|
||||
|
||||
return lista['tier']
|
||||
return lista["tier"]
|
||||
|
||||
|
||||
def load_json() -> dict:
|
||||
|
@ -129,18 +128,18 @@ def get_steam_id(title: str):
|
|||
|
||||
def check_time(): # this function check if it's time to update
|
||||
global file
|
||||
text = open(file, 'r')
|
||||
text = open(file, "r")
|
||||
json_table = json.load(text)
|
||||
text.close()
|
||||
|
||||
today = date.today()
|
||||
day = 0 # it controls how many days it's necessary for an update
|
||||
for i in 'ymd':
|
||||
if i == 'd':
|
||||
for i in "ymd":
|
||||
if i == "d":
|
||||
day = 7
|
||||
else:
|
||||
day = 0
|
||||
if int(today.strftime('%' + i)) > int(json_table['data'][i]) + day:
|
||||
if int(today.strftime("%" + i)) > int(json_table["data"][i]) + day:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
|
|
@ -10,7 +10,16 @@ from typing import Tuple, List
|
|||
|
||||
import qtawesome
|
||||
import requests
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QRunnable, QSettings, Qt, QFile, QDir
|
||||
from PyQt5.QtCore import (
|
||||
pyqtSignal,
|
||||
pyqtSlot,
|
||||
QObject,
|
||||
QRunnable,
|
||||
QSettings,
|
||||
Qt,
|
||||
QFile,
|
||||
QDir,
|
||||
)
|
||||
from PyQt5.QtGui import QPalette, QColor, QPixmap, QImage
|
||||
from PyQt5.QtWidgets import QApplication, QStyleFactory
|
||||
from requests.exceptions import HTTPError
|
||||
|
@ -25,6 +34,7 @@ if platform.system() == "Windows":
|
|||
from win32com.client import Dispatch # pylint: disable=E0401
|
||||
|
||||
from rare import image_dir, shared, resources_path
|
||||
|
||||
# Mac not supported
|
||||
|
||||
from legendary.core import LegendaryCore
|
||||
|
@ -66,27 +76,46 @@ def download_image(game, force=False):
|
|||
|
||||
# to get picture updates
|
||||
if not os.path.isfile(f"{image_dir}/{game.app_name}/image.json"):
|
||||
json_data = {"DieselGameBoxTall": None, "DieselGameBoxLogo": None, "Thumbnail": None}
|
||||
json_data = {
|
||||
"DieselGameBoxTall": None,
|
||||
"DieselGameBoxLogo": None,
|
||||
"Thumbnail": None,
|
||||
}
|
||||
else:
|
||||
json_data = json.load(open(f"{image_dir}/{game.app_name}/image.json", "r"))
|
||||
# Download
|
||||
for image in game.metadata["keyImages"]:
|
||||
if image["type"] == "DieselGameBoxTall" or image["type"] == "DieselGameBoxLogo" or image["type"] == "Thumbnail":
|
||||
if (
|
||||
image["type"] == "DieselGameBoxTall"
|
||||
or image["type"] == "DieselGameBoxLogo"
|
||||
or image["type"] == "Thumbnail"
|
||||
):
|
||||
if image["type"] not in json_data.keys():
|
||||
json_data[image["type"]] = None
|
||||
if json_data[image["type"]] != image["md5"] or not os.path.isfile(
|
||||
f"{image_dir}/{game.app_name}/{image['type']}.png"):
|
||||
f"{image_dir}/{game.app_name}/{image['type']}.png"
|
||||
):
|
||||
# Download
|
||||
json_data[image["type"]] = image["md5"]
|
||||
# os.remove(f"{image_dir}/{game.app_name}/{image['type']}.png")
|
||||
json.dump(json_data, open(f"{image_dir}/{game.app_name}/image.json", "w"))
|
||||
json.dump(
|
||||
json_data, open(f"{image_dir}/{game.app_name}/image.json", "w")
|
||||
)
|
||||
logger.info(f"Download Image for Game: {game.app_title}")
|
||||
url = image["url"]
|
||||
resp = requests.get(url)
|
||||
img = QImage()
|
||||
img.loadFromData(resp.content)
|
||||
img = img.scaled(200, 200 * 4 // 3, Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation)
|
||||
img.save(os.path.join(image_dir, game.app_name, image["type"] + ".png"), format="PNG")
|
||||
img = img.scaled(
|
||||
200,
|
||||
200 * 4 // 3,
|
||||
Qt.KeepAspectRatio,
|
||||
transformMode=Qt.SmoothTransformation,
|
||||
)
|
||||
img.save(
|
||||
os.path.join(image_dir, game.app_name, image["type"] + ".png"),
|
||||
format="PNG",
|
||||
)
|
||||
|
||||
|
||||
color_role_map = {
|
||||
|
@ -145,9 +174,13 @@ def load_color_scheme(path: str) -> QPalette:
|
|||
|
||||
def set_color_pallete(color_scheme: str):
|
||||
if not color_scheme:
|
||||
QApplication.instance().setStyle(QStyleFactory.create(QApplication.instance().property('rareDefaultQtStyle')))
|
||||
QApplication.instance().setStyle(
|
||||
QStyleFactory.create(QApplication.instance().property("rareDefaultQtStyle"))
|
||||
)
|
||||
QApplication.instance().setStyleSheet("")
|
||||
QApplication.instance().setPalette(QApplication.instance().style().standardPalette())
|
||||
QApplication.instance().setPalette(
|
||||
QApplication.instance().style().standardPalette()
|
||||
)
|
||||
return
|
||||
QApplication.instance().setStyle(QStyleFactory.create("Fusion"))
|
||||
custom_palette = load_color_scheme(f":/schemes/{color_scheme}")
|
||||
|
@ -166,7 +199,9 @@ def get_color_schemes() -> List[str]:
|
|||
|
||||
def set_style_sheet(style_sheet: str):
|
||||
if not style_sheet:
|
||||
QApplication.instance().setStyle(QStyleFactory.create(QApplication.instance().property('rareDefaultQtStyle')))
|
||||
QApplication.instance().setStyle(
|
||||
QStyleFactory.create(QApplication.instance().property("rareDefaultQtStyle"))
|
||||
)
|
||||
QApplication.instance().setStyleSheet("")
|
||||
return
|
||||
QApplication.instance().setStyle(QStyleFactory.create("Fusion"))
|
||||
|
@ -195,7 +230,9 @@ def get_translations():
|
|||
|
||||
def get_latest_version():
|
||||
try:
|
||||
resp = requests.get("https://api.github.com/repos/Dummerle/Rare/releases/latest")
|
||||
resp = requests.get(
|
||||
"https://api.github.com/repos/Dummerle/Rare/releases/latest"
|
||||
)
|
||||
tag = resp.json()["tag_name"]
|
||||
return tag
|
||||
except requests.exceptions.ConnectionError:
|
||||
|
@ -203,7 +240,7 @@ def get_latest_version():
|
|||
|
||||
|
||||
def get_size(b: int) -> str:
|
||||
for i in ['', 'K', 'M', 'G', 'T', 'P', 'E']:
|
||||
for i in ["", "K", "M", "G", "T", "P", "E"]:
|
||||
if b < 1024:
|
||||
return f"{b:.2f}{i}B"
|
||||
b /= 1024
|
||||
|
@ -226,21 +263,22 @@ def create_rare_desktop_link(type_of_link):
|
|||
else:
|
||||
executable = f"{sys.executable} {os.path.abspath(sys.argv[0])}"
|
||||
with open(os.path.join(path, "Rare.desktop"), "w") as desktop_file:
|
||||
desktop_file.write("[Desktop Entry]\n"
|
||||
f"Name=Rare\n"
|
||||
f"Type=Application\n"
|
||||
f"Icon={os.path.join(resources_path, 'images', 'Rare.png')}\n"
|
||||
f"Exec={executable}\n"
|
||||
"Terminal=false\n"
|
||||
"StartupWMClass=rare\n"
|
||||
)
|
||||
desktop_file.write(
|
||||
"[Desktop Entry]\n"
|
||||
f"Name=Rare\n"
|
||||
f"Type=Application\n"
|
||||
f"Icon={os.path.join(resources_path, 'images', 'Rare.png')}\n"
|
||||
f"Exec={executable}\n"
|
||||
"Terminal=false\n"
|
||||
"StartupWMClass=rare\n"
|
||||
)
|
||||
desktop_file.close()
|
||||
os.chmod(os.path.expanduser(os.path.join(path, "Rare.desktop")), 0o755)
|
||||
|
||||
elif platform.system() == "Windows":
|
||||
# Target of shortcut
|
||||
if type_of_link == "desktop":
|
||||
target_folder = os.path.expanduser('~/Desktop/')
|
||||
target_folder = os.path.expanduser("~/Desktop/")
|
||||
elif type_of_link == "start_menu":
|
||||
target_folder = os.path.expandvars("%appdata%/Microsoft/Windows/Start Menu")
|
||||
else:
|
||||
|
@ -255,7 +293,7 @@ def create_rare_desktop_link(type_of_link):
|
|||
executable = executable.replace("python.exe", "pythonw.exe")
|
||||
logger.debug(executable)
|
||||
# Add shortcut
|
||||
shell = Dispatch('WScript.Shell')
|
||||
shell = Dispatch("WScript.Shell")
|
||||
shortcut = shell.CreateShortCut(pathLink)
|
||||
shortcut.Targetpath = executable
|
||||
if not sys.executable.endswith("Rare.exe"):
|
||||
|
@ -270,12 +308,16 @@ def create_rare_desktop_link(type_of_link):
|
|||
def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop") -> bool:
|
||||
igame = core.get_installed_game(app_name)
|
||||
|
||||
if os.path.exists(p := os.path.join(image_dir, igame.app_name, 'Thumbnail.png')):
|
||||
if os.path.exists(p := os.path.join(image_dir, igame.app_name, "Thumbnail.png")):
|
||||
icon = p
|
||||
elif os.path.exists(p := os.path.join(image_dir, igame.app_name, "DieselGameBoxLogo.png")):
|
||||
elif os.path.exists(
|
||||
p := os.path.join(image_dir, igame.app_name, "DieselGameBoxLogo.png")
|
||||
):
|
||||
icon = p
|
||||
else:
|
||||
icon = os.path.join(os.path.join(image_dir, igame.app_name, "DieselGameBoxTall.png"))
|
||||
icon = os.path.join(
|
||||
os.path.join(image_dir, igame.app_name, "DieselGameBoxTall.png")
|
||||
)
|
||||
icon = icon.replace(".png", "")
|
||||
|
||||
# Linux
|
||||
|
@ -293,14 +335,15 @@ def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop") -
|
|||
else:
|
||||
executable = f"{sys.executable} {os.path.abspath(sys.argv[0])}"
|
||||
with open(f"{path}{igame.title}.desktop", "w") as desktop_file:
|
||||
desktop_file.write("[Desktop Entry]\n"
|
||||
f"Name={igame.title}\n"
|
||||
f"Type=Application\n"
|
||||
f"Icon={icon}.png\n"
|
||||
f"Exec={executable} launch {app_name}\n"
|
||||
"Terminal=false\n"
|
||||
"StartupWMClass=rare-game\n"
|
||||
)
|
||||
desktop_file.write(
|
||||
"[Desktop Entry]\n"
|
||||
f"Name={igame.title}\n"
|
||||
f"Type=Application\n"
|
||||
f"Icon={icon}.png\n"
|
||||
f"Exec={executable} launch {app_name}\n"
|
||||
"Terminal=false\n"
|
||||
"StartupWMClass=rare-game\n"
|
||||
)
|
||||
desktop_file.close()
|
||||
os.chmod(os.path.expanduser(f"{path}{igame.title}.desktop"), 0o755)
|
||||
|
||||
|
@ -308,7 +351,7 @@ def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop") -
|
|||
elif platform.system() == "Windows":
|
||||
# Target of shortcut
|
||||
if type_of_link == "desktop":
|
||||
target_folder = os.path.expanduser('~/Desktop/')
|
||||
target_folder = os.path.expanduser("~/Desktop/")
|
||||
elif type_of_link == "start_menu":
|
||||
target_folder = os.path.expandvars("%appdata%/Microsoft/Windows/Start Menu")
|
||||
else:
|
||||
|
@ -322,20 +365,20 @@ def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop") -
|
|||
for c in r'<>?":|\/*':
|
||||
linkName.replace(c, "")
|
||||
|
||||
linkName = linkName.strip() + '.lnk'
|
||||
linkName = linkName.strip() + ".lnk"
|
||||
|
||||
# Path to location of link file
|
||||
pathLink = os.path.join(target_folder, linkName)
|
||||
|
||||
# Add shortcut
|
||||
shell = Dispatch('WScript.Shell')
|
||||
shell = Dispatch("WScript.Shell")
|
||||
shortcut = shell.CreateShortCut(pathLink)
|
||||
if sys.executable.endswith("Rare.exe"):
|
||||
executable = sys.executable
|
||||
else:
|
||||
executable = f"{sys.executable} {os.path.abspath(sys.argv[0])}"
|
||||
shortcut.Targetpath = executable
|
||||
shortcut.Arguments = f'launch {app_name}'
|
||||
shortcut.Arguments = f"launch {app_name}"
|
||||
shortcut.WorkingDirectory = os.getcwd()
|
||||
|
||||
# Icon
|
||||
|
@ -383,9 +426,7 @@ def optimal_text_background(image: list) -> Tuple[int, int, int]:
|
|||
return tuple(inverted)
|
||||
|
||||
|
||||
def text_color_for_background(background: Tuple[int, int, int]) -> Tuple[int,
|
||||
int,
|
||||
int]:
|
||||
def text_color_for_background(background: Tuple[int, int, int]) -> Tuple[int, int, int]:
|
||||
"""
|
||||
Calculates whether a black or white text color would fit better for the
|
||||
given background, and returns that color. This is done by calculating the
|
||||
|
@ -393,10 +434,7 @@ def text_color_for_background(background: Tuple[int, int, int]) -> Tuple[int,
|
|||
"""
|
||||
# see https://alienryderflex.com/hsp.html
|
||||
(red, green, blue) = background
|
||||
luminance = math.sqrt(
|
||||
0.299 * red ** 2 +
|
||||
0.587 * green ** 2 +
|
||||
0.114 * blue ** 2)
|
||||
luminance = math.sqrt(0.299 * red ** 2 + 0.587 * green ** 2 + 0.114 * blue ** 2)
|
||||
if luminance < 127:
|
||||
return 255, 255, 255
|
||||
else:
|
||||
|
@ -408,45 +446,62 @@ class WineResolverSignals(QObject):
|
|||
|
||||
|
||||
class WineResolver(QRunnable):
|
||||
|
||||
def __init__(self, path: str, app_name: str, core: LegendaryCore):
|
||||
super(WineResolver, self).__init__()
|
||||
self.setAutoDelete(True)
|
||||
self.signals = WineResolverSignals()
|
||||
self.wine_env = os.environ.copy()
|
||||
self.wine_env.update(core.get_app_environment(app_name))
|
||||
self.wine_env['WINEDLLOVERRIDES'] = 'winemenubuilder=d;mscoree=d;mshtml=d;'
|
||||
self.wine_env['DISPLAY'] = ''
|
||||
self.wine_env["WINEDLLOVERRIDES"] = "winemenubuilder=d;mscoree=d;mshtml=d;"
|
||||
self.wine_env["DISPLAY"] = ""
|
||||
self.wine_binary = core.lgd.config.get(
|
||||
app_name, 'wine_executable',
|
||||
fallback=core.lgd.config.get('default', 'wine_executable', fallback='wine'))
|
||||
self.winepath_binary = os.path.join(os.path.dirname(self.wine_binary), 'winepath')
|
||||
app_name,
|
||||
"wine_executable",
|
||||
fallback=core.lgd.config.get("default", "wine_executable", fallback="wine"),
|
||||
)
|
||||
self.winepath_binary = os.path.join(
|
||||
os.path.dirname(self.wine_binary), "winepath"
|
||||
)
|
||||
self.path = PathSpec(core, app_name).cook(path)
|
||||
|
||||
@pyqtSlot()
|
||||
def run(self):
|
||||
if 'WINEPREFIX' not in self.wine_env or not os.path.exists(self.wine_env['WINEPREFIX']):
|
||||
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(str())
|
||||
return
|
||||
if not os.path.exists(self.wine_binary) or not os.path.exists(self.winepath_binary):
|
||||
if not os.path.exists(self.wine_binary) or not os.path.exists(
|
||||
self.winepath_binary
|
||||
):
|
||||
# pylint: disable=E1136
|
||||
self.signals.result_ready[str].emit(str())
|
||||
return
|
||||
path = self.path.strip().replace('/', '\\')
|
||||
path = self.path.strip().replace("/", "\\")
|
||||
# lk: if path does not exist form
|
||||
cmd = [self.wine_binary, 'cmd', '/c', 'echo', path]
|
||||
cmd = [self.wine_binary, "cmd", "/c", "echo", path]
|
||||
# lk: if path exists and needs a case sensitive interpretation form
|
||||
# cmd = [self.wine_binary, 'cmd', '/c', f'cd {path} & cd']
|
||||
proc = subprocess.Popen(cmd,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
env=self.wine_env, shell=False, text=True)
|
||||
proc = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=self.wine_env,
|
||||
shell=False,
|
||||
text=True,
|
||||
)
|
||||
out, err = proc.communicate()
|
||||
# Clean wine output
|
||||
out = out.strip().strip('"')
|
||||
proc = subprocess.Popen([self.winepath_binary, '-u', out],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
env=self.wine_env, shell=False, text=True)
|
||||
proc = subprocess.Popen(
|
||||
[self.winepath_binary, "-u", out],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=self.wine_env,
|
||||
shell=False,
|
||||
text=True,
|
||||
)
|
||||
out, err = proc.communicate()
|
||||
real_path = os.path.realpath(out.strip())
|
||||
# pylint: disable=E1136
|
||||
|
@ -474,7 +529,11 @@ class CloudWorker(QRunnable):
|
|||
|
||||
def get_raw_save_path(game: Game):
|
||||
if game.supports_cloud_saves:
|
||||
return game.metadata.get("customAttributes", {}).get("CloudSaveFolder", {}).get("value")
|
||||
return (
|
||||
game.metadata.get("customAttributes", {})
|
||||
.get("CloudSaveFolder", {})
|
||||
.get("value")
|
||||
)
|
||||
|
||||
|
||||
def get_default_platform(app_name):
|
||||
|
|
Loading…
Reference in a new issue