1
0
Fork 0
mirror of synced 2024-06-02 10:44:40 +12:00

Rare: pass through Black formatter

This commit is contained in:
Stelios Tsampas 2021-12-24 11:09:50 +02:00 committed by Dummerle
parent 0d2f36e028
commit 8f89eb6e88
No known key found for this signature in database
GPG key ID: AB68CC59CA39F2F1
60 changed files with 2128 additions and 957 deletions

View file

@ -10,6 +10,7 @@ def main():
# fix cx_freeze # fix cx_freeze
import multiprocessing import multiprocessing
multiprocessing.freeze_support() multiprocessing.freeze_support()
# insert legendary for installed via pip/setup.py submodule to path # insert legendary for installed via pip/setup.py submodule to path
@ -18,39 +19,61 @@ def main():
# CLI Options # CLI Options
parser = ArgumentParser() parser = ArgumentParser()
parser.add_argument("-V", "--version", action="store_true", help="Shows version and exits") parser.add_argument(
parser.add_argument("-S", "--silent", action="store_true", "-V", "--version", action="store_true", help="Shows version and exits"
help="Launch Rare in background. Open it from System Tray Icon") )
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("--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(
parser.add_argument("--test-start", action="store_true", help="Quit immediately after launch") "--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", parser.add_argument(
help="Use this, if there is no link on desktop to start Rare") "--desktop-shortcut",
parser.add_argument("--startmenu-shortcut", action="store_true", dest="startmenu_shortcut", action="store_true",
help="Use this, if there is no link in start menu to launch Rare") 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") subparsers = parser.add_subparsers(title="Commands", dest="subparser")
launch_parser = subparsers.add_parser("launch") 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() args = parser.parse_args()
if args.desktop_shortcut: if args.desktop_shortcut:
from rare.utils import utils from rare.utils import utils
utils.create_rare_desktop_link("desktop") utils.create_rare_desktop_link("desktop")
print("Link created") print("Link created")
if args.startmenu_shortcut: if args.startmenu_shortcut:
from rare.utils import utils from rare.utils import utils
utils.create_rare_desktop_link("start_menu") utils.create_rare_desktop_link("start_menu")
print("link created") print("link created")
if args.version: if args.version:
from rare import __version__, code_name from rare import __version__, code_name
print(f"Rare {__version__} Codename: {code_name}") print(f"Rare {__version__} Codename: {code_name}")
return return
from rare.utils import singleton from rare.utils import singleton
try: try:
# this object only allows one instance per machine # this object only allows one instance per machine
@ -58,6 +81,7 @@ def main():
except singleton.SingleInstanceException: except singleton.SingleInstanceException:
print("Rare is already running") print("Rare is already running")
from rare import data_dir from rare import data_dir
with open(os.path.join(data_dir, "lockfile"), "w") as file: with open(os.path.join(data_dir, "lockfile"), "w") as file:
if args.subparser == "launch": if args.subparser == "launch":
file.write("launch " + args.app_name) file.write("launch " + args.app_name)
@ -70,13 +94,16 @@ def main():
args.silent = True args.silent = True
from rare.app import start from rare.app import start
start(args) start(args)
if __name__ == '__main__': if __name__ == "__main__":
# run from source # run from source
# insert raw legendary submodule # 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 # insert source directory
sys.path.insert(0, str(pathlib.Path(__file__).parents[1].absolute())) sys.path.insert(0, str(pathlib.Path(__file__).parents[1].absolute()))

View file

@ -12,6 +12,7 @@ from PyQt5.QtWidgets import QApplication, QSystemTrayIcon, QMessageBox
from requests import HTTPError from requests import HTTPError
import legendary import legendary
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import rare.resources.resources import rare.resources.resources
import rare.shared as shared 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.components.tray_icon import TrayIcon
from rare.utils.utils import set_color_pallete, set_style_sheet 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") file_name = os.path.join(cache_dir, "logs", f"Rare_{start_time}.log")
if not os.path.exists(os.path.dirname(file_name)): if not os.path.exists(os.path.dirname(file_name)):
os.makedirs(os.path.dirname(file_name)) os.makedirs(os.path.dirname(file_name))
@ -63,10 +64,10 @@ class App(QApplication):
self.core = shared.init_legendary() self.core = shared.init_legendary()
except configparser.MissingSectionHeaderError as e: except configparser.MissingSectionHeaderError as e:
logger.warning(f"Config is corrupt: {e}") logger.warning(f"Config is corrupt: {e}")
if config_path := os.environ.get('XDG_CONFIG_HOME'): if config_path := os.environ.get("XDG_CONFIG_HOME"):
path = os.path.join(config_path, 'legendary') path = os.path.join(config_path, "legendary")
else: 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: with open(os.path.join(path, "config.ini"), "w") as config_file:
config_file.write("[Legendary]") config_file.write("[Legendary]")
self.core = shared.init_legendary() self.core = shared.init_legendary()
@ -95,12 +96,15 @@ class App(QApplication):
self.signals.exit_app.connect(self.exit_app) self.signals.exit_app.connect(self.exit_app)
self.signals.send_notification.connect( self.signals.send_notification.connect(
lambda title: lambda title: self.tray_icon.showMessage(
self.tray_icon.showMessage(
self.tr("Download finished"), self.tr("Download finished"),
self.tr("Download finished. {} is playable now").format(title), self.tr("Download finished. {} is playable now").format(title),
QSystemTrayIcon.Information, 4000) QSystemTrayIcon.Information,
if self.settings.value("notification", True, bool) else None) 4000,
)
if self.settings.value("notification", True, bool)
else None
)
# Translator # Translator
self.translator = QTranslator() self.translator = QTranslator()
@ -122,9 +126,12 @@ class App(QApplication):
# Style # Style
# lk: this is a bit silly but serves well until we have a class # 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 # 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("color_scheme", "")
self.settings.setValue("style_sheet", "RareStyle") self.settings.setValue("style_sheet", "RareStyle")
@ -157,23 +164,38 @@ class App(QApplication):
self.tray_icon = TrayIcon(self) self.tray_icon = TrayIcon(self)
self.tray_icon.exit_action.triggered.connect(self.exit_app) self.tray_icon.exit_action.triggered.connect(self.exit_app)
self.tray_icon.start_rare.triggered.connect(self.show_mainwindow) 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: if not self.args.silent:
self.mainwindow.show_window_centralized() self.mainwindow.show_window_centralized()
self.window_launched = True self.window_launched = True
if shared.args.subparser == "launch": if shared.args.subparser == "launch":
if shared.args.app_name in [i.app_name for i in self.core.get_installed_list()]: if shared.args.app_name in [
logger.info("Launching " + self.core.get_installed_game(shared.args.app_name).title) i.app_name for i in self.core.get_installed_list()
self.mainwindow.tab_widget.games_tab.game_utils.prepare_launch(shared.args.app_name) ]:
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: else:
logger.error( logger.error(
f"Could not find {shared.args.app_name} in Games or it is not installed") f"Could not find {shared.args.app_name} in Games or it is not installed"
QMessageBox.warning(self.mainwindow, "Warning", )
self.tr( QMessageBox.warning(
"Could not find {} in installed games. Did you modify the shortcut? ").format( self.mainwindow,
shared.args.app_name)) "Warning",
self.tr(
"Could not find {} in installed games. Did you modify the shortcut? "
).format(shared.args.app_name),
)
if shared.args.test_start: if shared.args.test_start:
self.exit_app(0) self.exit_app(0)
@ -190,8 +212,12 @@ class App(QApplication):
question = QMessageBox.question( question = QMessageBox.question(
self.mainwindow, self.mainwindow,
self.tr("Close"), self.tr("Close"),
self.tr("There is a download active. Do you really want to exit app?"), self.tr(
QMessageBox.Yes, QMessageBox.No) "There is a download active. Do you really want to exit app?"
),
QMessageBox.Yes,
QMessageBox.No,
)
if question == QMessageBox.No: if question == QMessageBox.No:
return return
else: else:
@ -218,19 +244,23 @@ def start(args):
# configure logging # configure logging
if args.debug: 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) logging.getLogger().setLevel(level=logging.DEBUG)
# keep requests, asyncio and pillow quiet # keep requests, asyncio and pillow quiet
logging.getLogger('requests').setLevel(logging.WARNING) logging.getLogger("requests").setLevel(logging.WARNING)
logging.getLogger('urllib3').setLevel(logging.WARNING) logging.getLogger("urllib3").setLevel(logging.WARNING)
logging.getLogger("asyncio").setLevel(logging.WARNING) logging.getLogger("asyncio").setLevel(logging.WARNING)
logger.info(f"Launching Rare version {rare.__version__} Codename: {rare.code_name}\n" logger.info(
f"Using Legendary {legendary.__version__} Codename: {legendary.__codename__} as backend\n" f"Launching Rare version {rare.__version__} Codename: {rare.code_name}\n"
f"Operating System: {platform.system()}, Python version: {platform.python_version()}\n" f"Using Legendary {legendary.__version__} Codename: {legendary.__codename__} as backend\n"
f"Running {sys.executable} {' '.join(sys.argv)}") f"Operating System: {platform.system()}, Python version: {platform.python_version()}\n"
f"Running {sys.executable} {' '.join(sys.argv)}"
)
else: else:
logging.basicConfig( logging.basicConfig(
format='[%(name)s] %(levelname)s: %(message)s', format="[%(name)s] %(levelname)s: %(message)s",
level=logging.INFO, level=logging.INFO,
filename=file_name, filename=file_name,
) )

View file

@ -18,7 +18,9 @@ from rare.utils.utils import get_size
class InstallDialog(QDialog, Ui_InstallDialog): class InstallDialog(QDialog, Ui_InstallDialog):
result_ready = pyqtSignal(InstallQueueItemModel) 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) super(InstallDialog, self).__init__(parent)
self.setupUi(self) self.setupUi(self)
self.setAttribute(Qt.WA_DeleteOnClose, True) self.setAttribute(Qt.WA_DeleteOnClose, True)
@ -40,17 +42,19 @@ class InstallDialog(QDialog, Ui_InstallDialog):
self.threadpool.setMaxThreadCount(1) self.threadpool.setMaxThreadCount(1)
header = self.tr("Update") if update else self.tr("Install") header = self.tr("Update") if update else self.tr("Install")
self.install_dialog_label.setText(f"<h3>{header} \"{self.game.app_title}\"</h3>") self.install_dialog_label.setText(f'<h3>{header} "{self.game.app_title}"</h3>')
self.setWindowTitle(f"{self.windowTitle()} - {header} \"{self.game.app_title}\"") self.setWindowTitle(f'{self.windowTitle()} - {header} "{self.game.app_title}"')
default_path = os.path.expanduser("~/legendary") default_path = os.path.expanduser("~/legendary")
if self.core.lgd.config.has_option("Legendary", "install_dir"): if self.core.lgd.config.has_option("Legendary", "install_dir"):
default_path = self.core.lgd.config.get("Legendary", "install_dir") default_path = self.core.lgd.config.get("Legendary", "install_dir")
self.install_dir_edit = PathEdit(path=default_path, self.install_dir_edit = PathEdit(
file_type=QFileDialog.DirectoryOnly, path=default_path,
edit_func=self.option_changed, file_type=QFileDialog.DirectoryOnly,
parent=self) edit_func=self.option_changed,
parent=self,
)
self.install_dir_layout.addWidget(self.install_dir_edit) self.install_dir_layout.addWidget(self.install_dir_edit)
if self.update: if self.update:
@ -66,10 +70,23 @@ class InstallDialog(QDialog, Ui_InstallDialog):
if dl_item.options.app_name in shared.api_results.mac_games: if dl_item.options.app_name in shared.api_results.mac_games:
platforms.append("Mac") platforms.append("Mac")
self.platform_combo_box.addItems(platforms) self.platform_combo_box.addItems(platforms)
self.platform_combo_box.currentIndexChanged.connect(lambda: self.option_changed(None)) self.platform_combo_box.currentIndexChanged.connect(
self.platform_combo_box.currentIndexChanged.connect(lambda i: QMessageBox.warning(self, "Warning", self.tr( lambda: self.option_changed(None)
"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 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: if platform.system() == "Darwin" and "Mac" in platforms:
self.platform_combo_box.setCurrentIndex(platforms.index("Mac")) 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.force_download_check.stateChanged.connect(self.option_changed)
self.ignore_space_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() self.sdl_list_checks = list()
try: try:
for key, info in games[self.app_name].items(): for key, info in games[self.app_name].items():
cb = QDataCheckBox(info['name'], info['tags']) cb = QDataCheckBox(info["name"], info["tags"])
if key == '__required': if key == "__required":
self.dl_item.options.sdl_list.extend(info['tags']) self.dl_item.options.sdl_list.extend(info["tags"])
cb.setChecked(True) cb.setChecked(True)
cb.setDisabled(True) cb.setDisabled(True)
self.sdl_list_layout.addWidget(cb) self.sdl_list_layout.addWidget(cb)
@ -120,13 +139,15 @@ class InstallDialog(QDialog, Ui_InstallDialog):
self.show() self.show()
def get_options(self): 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.max_workers = self.max_workers_spin.value()
self.dl_item.options.force = self.force_download_check.isChecked() 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.ignore_space_req = self.ignore_space_check.isChecked()
self.dl_item.options.no_install = self.download_only_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.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: for cb in self.sdl_list_checks:
if data := cb.isChecked(): if data := cb.isChecked():
# noinspection PyTypeChecker # noinspection PyTypeChecker
@ -145,9 +166,13 @@ class InstallDialog(QDialog, Ui_InstallDialog):
def verify_clicked(self): def verify_clicked(self):
message = self.tr("Updating...") message = self.tr("Updating...")
self.download_size_info_label.setText(message) 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.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.cancel_button.setEnabled(False)
self.verify_button.setEnabled(False) self.verify_button.setEnabled(False)
self.install_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 install_size = self.dl_item.download.analysis.install_size
if download_size: if download_size:
self.download_size_info_label.setText("{}".format(get_size(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) self.install_button.setEnabled(not self.options_changed)
else: else:
self.install_size_info_label.setText(self.tr("Game already installed")) 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.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.verify_button.setEnabled(self.options_changed)
self.cancel_button.setEnabled(True) self.cancel_button.setEnabled(True)
if self.silent: if self.silent:
@ -225,7 +256,6 @@ class InstallInfoWorkerSignals(QObject):
class InstallInfoWorker(QRunnable): class InstallInfoWorker(QRunnable):
def __init__(self, core: LegendaryCore, dl_item: InstallQueueItemModel): def __init__(self, core: LegendaryCore, dl_item: InstallQueueItemModel):
super(InstallInfoWorker, self).__init__() super(InstallInfoWorker, self).__init__()
self.signals = InstallInfoWorkerSignals() self.signals = InstallInfoWorkerSignals()
@ -235,44 +265,47 @@ class InstallInfoWorker(QRunnable):
@pyqtSlot() @pyqtSlot()
def run(self): def run(self):
try: try:
download = InstallDownloadModel(*self.core.prepare_download( download = InstallDownloadModel(
app_name=self.dl_item.options.app_name, *self.core.prepare_download(
base_path=self.dl_item.options.base_path, app_name=self.dl_item.options.app_name,
force=self.dl_item.options.force, base_path=self.dl_item.options.base_path,
no_install=self.dl_item.options.no_install, force=self.dl_item.options.force,
status_q=self.dl_item.status_q, no_install=self.dl_item.options.no_install,
# max_shm=, status_q=self.dl_item.status_q,
max_workers=self.dl_item.options.max_workers, # max_shm=,
# game_folder=, max_workers=self.dl_item.options.max_workers,
# disable_patching=, # game_folder=,
# override_manifest=, # disable_patching=,
# override_old_manifest=, # override_manifest=,
# override_base_url=, # override_old_manifest=,
platform=self.dl_item.options.platform, # override_base_url=,
# file_prefix_filter=, platform=self.dl_item.options.platform,
# file_exclude_filter=, # file_prefix_filter=,
# file_install_tag=, # file_exclude_filter=,
# dl_optimizations=, # file_install_tag=,
# dl_timeout=, # dl_optimizations=,
repair=self.dl_item.options.repair, # dl_timeout=,
# repair_use_latest=, repair=self.dl_item.options.repair,
ignore_space_req=self.dl_item.options.ignore_space_req, # repair_use_latest=,
# disable_delta=, ignore_space_req=self.dl_item.options.ignore_space_req,
# override_delta_manifest=, # disable_delta=,
# reset_sdl=, # override_delta_manifest=,
sdl_prompt=lambda app_name, title: self.dl_item.options.sdl_list # reset_sdl=,
)) sdl_prompt=lambda app_name, title: self.dl_item.options.sdl_list,
)
)
if not download.res.failures: if not download.res.failures:
self.signals.result.emit(download) self.signals.result.emit(download)
else: 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: except Exception as e:
self.signals.failed.emit(str(e)) self.signals.failed.emit(str(e))
self.signals.finished.emit() self.signals.finished.emit()
class QDataCheckBox(QCheckBox): class QDataCheckBox(QCheckBox):
def __init__(self, text, data=None, parent=None): def __init__(self, text, data=None, parent=None):
super(QDataCheckBox, self).__init__(parent) super(QDataCheckBox, self).__init__(parent)
self.setText(text) self.setText(text)

View file

@ -75,8 +75,8 @@ class AssetWorker(QRunnable):
if not shared.core.egs.user: if not shared.core.egs.user:
return [] return []
assets = [ assets = [
GameAsset.from_egs_json(a) for a in GameAsset.from_egs_json(a)
shared.core.egs.get_game_assets(platform=p) for a in shared.core.egs.get_game_assets(platform=p)
] ]
return assets 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 save from another worker, because it is used in cloud_save_utils too
cloud_worker = CloudWorker() 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) self.thread_pool.start(cloud_worker)
else: else:
self.finished = 2 self.finished = 2
if self.core.lgd.assets: 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.game_list,
self.api_results.mac_games = list(map(lambda i: i.app_name, self.core.get_game_list(False, "Mac"))) 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: else:
logger.warning("No assets found. Falling back to empty game lists") logger.warning("No assets found. Falling back to empty game lists")
self.api_results.game_list, self.api_results.dlcs = [], {} self.api_results.game_list, self.api_results.dlcs = [], {}
@ -165,11 +174,18 @@ class LaunchDialog(QDialog, Ui_LaunchDialog):
if result: if result:
self.api_results.game_list, self.api_results.dlcs = result self.api_results.game_list, self.api_results.dlcs = result
else: 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": 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": 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": elif text == "no_assets":
self.api_results.no_asset_games = result if result else [] self.api_results.no_asset_games = result if result else []

View file

@ -48,8 +48,12 @@ class LoginDialog(QDialog, Ui_LoginDialog):
self.next_button.setEnabled(False) self.next_button.setEnabled(False)
self.back_button.setEnabled(False) self.back_button.setEnabled(False)
self.login_browser_radio.clicked.connect(lambda: self.next_button.setEnabled(True)) self.login_browser_radio.clicked.connect(
self.login_import_radio.clicked.connect(lambda: self.next_button.setEnabled(True)) 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.exit_button.clicked.connect(self.close)
self.back_button.clicked.connect(self.back_clicked) self.back_button.clicked.connect(self.back_clicked)
self.next_button.clicked.connect(self.next_clicked) self.next_button.clicked.connect(self.next_clicked)
@ -97,4 +101,3 @@ class LoginDialog(QDialog, Ui_LoginDialog):
self.next_button.setEnabled(False) self.next_button.setEnabled(False)
self.logged_in = False self.logged_in = False
QMessageBox.warning(self, "Error", str(e)) QMessageBox.warning(self, "Error", str(e))

View file

@ -25,9 +25,7 @@ class BrowserLogin(QWidget, Ui_BrowserLogin):
self.core = core self.core = core
self.sid_edit = IndicatorLineEdit( self.sid_edit = IndicatorLineEdit(
ph_text=self.tr("Insert SID here"), ph_text=self.tr("Insert SID here"), edit_func=self.text_changed, parent=self
edit_func=self.text_changed,
parent=self
) )
self.sid_layout.addWidget(self.sid_edit) self.sid_layout.addWidget(self.sid_edit)
@ -58,7 +56,9 @@ class BrowserLogin(QWidget, Ui_BrowserLogin):
try: try:
token = self.core.auth_sid(sid) token = self.core.auth_sid(sid)
if self.core.auth_code(token): 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() self.success.emit()
else: else:
self.status_label.setText(self.tr("Login failed.")) self.status_label.setText(self.tr("Login failed."))

View file

@ -17,7 +17,9 @@ class ImportLogin(QWidget, Ui_ImportLogin):
if os.name == "nt": if os.name == "nt":
localappdata = os.path.expandvars("%LOCALAPPDATA%") localappdata = os.path.expandvars("%LOCALAPPDATA%")
else: 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") appdata_path = os.path.join(localappdata, "EpicGamesLauncher/Saved/Config/Windows")
found = False found = False
@ -27,7 +29,9 @@ class ImportLogin(QWidget, Ui_ImportLogin):
self.core = core 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. ") self.text_egl_notfound = self.tr("Could not find EGL Program Data. ")
if os.name == "nt": if os.name == "nt":
@ -39,14 +43,19 @@ class ImportLogin(QWidget, Ui_ImportLogin):
self.status_label.setText(self.text_egl_found) self.status_label.setText(self.text_egl_found)
self.found = True self.found = True
else: else:
self.info_label.setText(self.tr( self.info_label.setText(
"Please select the Wine prefix" self.tr(
" where Epic Games Launcher is installed. ") + self.info_label.text() "Please select the Wine prefix"
) " where Epic Games Launcher is installed. "
)
+ self.info_label.text()
)
prefixes = self.get_wine_prefixes() prefixes = self.get_wine_prefixes()
if len(prefixes): if len(prefixes):
self.prefix_combo.addItems(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: else:
self.status_label.setText(self.text_egl_notfound) self.status_label.setText(self.text_egl_notfound)
@ -65,7 +74,9 @@ class ImportLogin(QWidget, Ui_ImportLogin):
return prefixes return prefixes
def prefix_path(self): 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) prefix_dialog.setFileMode(QFileDialog.DirectoryOnly)
if prefix_dialog.exec_(): if prefix_dialog.exec_():
names = prefix_dialog.selectedFiles() names = prefix_dialog.selectedFiles()
@ -75,14 +86,18 @@ class ImportLogin(QWidget, Ui_ImportLogin):
if os.name == "nt": if os.name == "nt":
return self.found return self.found
else: 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): def do_login(self):
self.status_label.setText(self.tr("Loading...")) self.status_label.setText(self.tr("Loading..."))
if os.name == "nt": if os.name == "nt":
pass pass
else: 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: try:
if self.core.auth_import(): if self.core.auth_import():
logger.info(f"Logged in as {self.core.lgd.userdata['displayName']}") logger.info(f"Logged in as {self.core.lgd.userdata['displayName']}")

View file

@ -1,5 +1,12 @@
from PyQt5.QtCore import Qt 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 from rare.utils.extra_widgets import PathEdit

View file

@ -1,5 +1,13 @@
from PyQt5.QtCore import Qt 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 qtawesome import icon
from legendary.models.game import Game from legendary.models.game import Game
@ -12,7 +20,9 @@ class UninstallDialog(QDialog):
self.info = 0 self.info = 0
self.setAttribute(Qt.WA_DeleteOnClose, True) self.setAttribute(Qt.WA_DeleteOnClose, True)
self.layout = QVBoxLayout() 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.layout.addWidget(self.info_text)
self.keep_files = QCheckBox(self.tr("Keep Files")) self.keep_files = QCheckBox(self.tr("Keep Files"))
self.form = QFormLayout() self.form = QFormLayout()
@ -21,7 +31,9 @@ class UninstallDialog(QDialog):
self.layout.addLayout(self.form) self.layout.addLayout(self.form)
self.button_layout = QHBoxLayout() 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.ok_button.clicked.connect(self.ok)
self.cancel_button = QPushButton(self.tr("Cancel")) self.cancel_button = QPushButton(self.tr("Cancel"))

View file

@ -1,5 +1,11 @@
from PyQt5.QtGui import QTextCursor, QFont 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): class ConsoleWindow(QWidget):
@ -22,7 +28,9 @@ class ConsoleWindow(QWidget):
self.setLayout(self.layout) self.setLayout(self.layout)
def save(self): 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 ok:
if "." not in file: if "." not in file:
file += ".log" file += ".log"
@ -39,7 +47,6 @@ class ConsoleWindow(QWidget):
class Console(QPlainTextEdit): class Console(QPlainTextEdit):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.setReadOnly(True) self.setReadOnly(True)
@ -51,12 +58,13 @@ class Console(QPlainTextEdit):
self.scroll_to_last_line() self.scroll_to_last_line()
def error(self, text): 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() self.scroll_to_last_line()
def scroll_to_last_line(self): def scroll_to_last_line(self):
cursor = self.textCursor() cursor = self.textCursor()
cursor.movePosition(QTextCursor.End) cursor.movePosition(QTextCursor.End)
cursor.movePosition(QTextCursor.Up if cursor.atBlockStart() else cursor.movePosition(
QTextCursor.StartOfLine) QTextCursor.Up if cursor.atBlockStart() else QTextCursor.StartOfLine
)
self.setTextCursor(cursor) self.setTextCursor(cursor)

View file

@ -13,7 +13,6 @@ logger = getLogger("Window")
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
def __init__(self): def __init__(self):
super(MainWindow, self).__init__() super(MainWindow, self).__init__()
self.setAttribute(Qt.WA_DeleteOnClose) self.setAttribute(Qt.WA_DeleteOnClose)
@ -54,10 +53,14 @@ class MainWindow(QMainWindow):
decor_width = margins.left() + margins.right() decor_width = margins.left() + margins.right()
decor_height = margins.top() + margins.bottom() decor_height = margins.top() + margins.bottom()
window_size = QSize(self.width(), self.height()).boundedTo( 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.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): def timer_finished(self):
file_path = os.path.join(data_dir, "lockfile") file_path = os.path.join(data_dir, "lockfile")
@ -67,8 +70,12 @@ class MainWindow(QMainWindow):
file.close() file.close()
if action.startswith("launch"): if action.startswith("launch"):
game = action.replace("launch ", "").replace("\n", "") 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): if game in [
self.tab_widget.games_tab.game_utils.prepare_launch(game, offline=shared.args.offline) 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: else:
logger.info(f"Could not find {game} in Games") logger.info(f"Could not find {game} in Games")
elif action.startswith("start"): elif action.startswith("start"):

View file

@ -29,8 +29,15 @@ class TabWidget(QTabWidget):
if not shared.args.offline: if not shared.args.offline:
# updates = self.games_tab.default_widget.game_list.updates # updates = self.games_tab.default_widget.game_list.updates
self.downloadTab = DownloadsTab(self.games_tab.updates) self.downloadTab = DownloadsTab(self.games_tab.updates)
self.addTab(self.downloadTab, "Downloads" + ( self.addTab(
" (" + str(len(self.games_tab.updates)) + ")" if len(self.games_tab.updates) != 0 else "")) self.downloadTab,
"Downloads"
+ (
" (" + str(len(self.games_tab.updates)) + ")"
if len(self.games_tab.updates) != 0
else ""
),
)
self.store = Shop(self.core) self.store = Shop(self.core)
self.addTab(self.store, self.tr("Store (Beta)")) self.addTab(self.store, self.tr("Store (Beta)"))
@ -49,14 +56,18 @@ class TabWidget(QTabWidget):
self.mini_widget = MiniWidget() self.mini_widget = MiniWidget()
account_action = QWidgetAction(self) account_action = QWidgetAction(self)
account_action.setDefaultWidget(self.mini_widget) 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.setMenu(QMenu())
account_button.menu().addAction(account_action) 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.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 # Signals
# set current index # set current index
self.signals.set_main_tab_index.connect(self.setCurrentIndex) 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)) QShortcut("Alt+4", self).activated.connect(lambda: self.setCurrentIndex(5))
def update_dl_tab_text(self): 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 num_downloads = len(
self.downloadTab.update_widgets.keys()])) 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: if num_downloads != 0:
self.setTabText(1, f"Downloads ({num_downloads})") self.setTabText(1, f"Downloads ({num_downloads})")

View file

@ -20,7 +20,10 @@ class MiniWidget(QWidget):
self.open_browser = QPushButton(self.tr("Account settings")) self.open_browser = QPushButton(self.tr("Account settings"))
self.open_browser.clicked.connect( 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.layout.addWidget(self.open_browser)
self.logout_button = QPushButton(self.tr("Logout")) self.logout_button = QPushButton(self.tr("Logout"))
@ -29,9 +32,13 @@ class MiniWidget(QWidget):
self.setLayout(self.layout) self.setLayout(self.layout)
def logout(self): def logout(self):
reply = QMessageBox.question(self.parent().parent(), 'Message', reply = QMessageBox.question(
self.tr("Do you really want to logout"), QMessageBox.Yes | self.parent().parent(),
QMessageBox.No, QMessageBox.No) "Message",
self.tr("Do you really want to logout"),
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No,
)
if reply == QMessageBox.Yes: if reply == QMessageBox.Yes:
self.core.lgd.invalidate_userdata() self.core.lgd.invalidate_userdata()

View file

@ -3,8 +3,14 @@ from logging import getLogger
from typing import List, Dict from typing import List, Dict
from PyQt5.QtCore import QThread, pyqtSignal, QSettings from PyQt5.QtCore import QThread, pyqtSignal, QSettings
from PyQt5.QtWidgets import QWidget, QMessageBox, QVBoxLayout, QLabel, QPushButton, \ from PyQt5.QtWidgets import (
QGroupBox QWidget,
QMessageBox,
QVBoxLayout,
QLabel,
QPushButton,
QGroupBox,
)
from legendary.core import LegendaryCore from legendary.core import LegendaryCore
from legendary.models.downloading import UIUpdate 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.queue_item_removed)
self.signals.game_uninstalled.connect(self.remove_update) 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) shared.signals.game_uninstalled.connect(self.game_uninstalled)
self.reset_infos() self.reset_infos()
@ -75,7 +83,9 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
self.update_widgets[igame.app_name] = widget self.update_widgets[igame.app_name] = widget
widget.update_signal.connect(self.get_install_options) widget.update_signal.connect(self.get_install_options)
if QSettings().value("auto_update", False, bool): 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) widget.update_button.setDisabled(True)
self.update_text.setVisible(False) self.update_text.setVisible(False)
@ -148,7 +158,12 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
if game.app_name in self.update_widgets.keys(): if game.app_name in self.update_widgets.keys():
igame = self.core.get_installed_game(game.app_name) 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.remove_update(game.app_name)
self.signals.send_notification.emit(game.app_title) self.signals.send_notification.emit(game.app_title)
@ -188,12 +203,20 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
self.analysis = None self.analysis = None
def statistics(self, ui_update: UIUpdate): 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.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.cache_used.setText(
self.downloaded.setText(f"{get_size(ui_update.total_downloaded)} / {get_size(self.analysis.dl_size)}") 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.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: def get_time(self, seconds: int) -> str:
return str(datetime.timedelta(seconds=seconds)) return str(datetime.timedelta(seconds=seconds))
@ -209,14 +232,24 @@ class DownloadsTab(QWidget, Ui_DownloadsTab):
def get_install_options(self, options: InstallOptionsModel): def get_install_options(self, options: InstallOptionsModel):
install_dialog = InstallDialog(InstallQueueItemModel(options=options), install_dialog = InstallDialog(
update=options.update, silent=options.silent, parent=self) InstallQueueItemModel(options=options),
update=options.update,
silent=options.silent,
parent=self,
)
install_dialog.result_ready.connect(self.on_install_dialog_closed) install_dialog.result_ready.connect(self.on_install_dialog_closed)
install_dialog.execute() install_dialog.execute()
def start_download(self, download_item: InstallQueueItemModel): def start_download(self, download_item: InstallQueueItemModel):
downloads = len(self.downloadTab.dl_queue) + len(self.downloadTab.update_widgets.keys()) + 1 downloads = (
self.setTabText(1, "Downloads" + ((" (" + str(downloads) + ")") if downloads != 0 else "")) 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.setCurrentIndex(1)
self.downloadTab.install_game(download_item) self.downloadTab.install_game(download_item)
self.games_tab.start_download(download_item.options.app_name) 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.update_with_settings.clicked.connect(lambda: self.update_game(False))
self.layout.addWidget(self.update_button) self.layout.addWidget(self.update_button)
self.layout.addWidget(self.update_with_settings) self.layout.addWidget(self.update_with_settings)
self.layout.addWidget(QLabel( self.layout.addWidget(
self.tr("Version: ") + self.game.version + " -> " + QLabel(
self.core.get_asset(self.game.app_name, self.game.platform, True).build_version)) self.tr("Version: ")
+ self.game.version
+ " -> "
+ self.core.get_asset(
self.game.app_name, self.game.platform, True
).build_version
)
)
self.setLayout(self.layout) self.setLayout(self.layout)
def update_game(self, auto: bool): def update_game(self, auto: bool):
self.update_button.setDisabled(True) self.update_button.setDisabled(True)
self.update_with_settings.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

View file

@ -1,7 +1,14 @@
from logging import getLogger from logging import getLogger
from PyQt5.QtCore import pyqtSignal 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 qtawesome import icon
from rare.utils.models import InstallQueueItemModel from rare.utils.models import InstallQueueItemModel
@ -28,7 +35,9 @@ class DlWidget(QWidget):
self.left_layout.addWidget(self.move_up_button) self.left_layout.addWidget(self.move_up_button)
self.move_down_buttton = QPushButton(icon("fa.arrow-down"), "") 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.left_layout.addWidget(self.move_down_buttton)
self.move_down_buttton.setFixedWidth(20) self.move_down_buttton.setFixedWidth(20)
@ -43,8 +52,18 @@ class DlWidget(QWidget):
self.size = QHBoxLayout() self.size = QHBoxLayout()
self.size.addWidget(QLabel(self.tr("Download size: {} GB").format(round(dl_size / 1024 ** 3, 2)))) self.size.addWidget(
self.size.addWidget(QLabel(self.tr("Install size: {} GB").format(round(install_size / 1024 ** 3, 2)))) 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.right_layout.addLayout(self.size)
self.delete = QPushButton(self.tr("Remove Download")) self.delete = QPushButton(self.tr("Remove Download"))
@ -69,7 +88,9 @@ class DlQueueWidget(QGroupBox):
self.layout().addWidget(self.text) self.layout().addWidget(self.text)
def update_queue(self, dl_queue: list): 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 self.dl_queue = dl_queue
for item in (self.layout().itemAt(i) for i in range(self.layout().count())): for item in (self.layout().itemAt(i) for i in range(self.layout().count())):

View file

@ -59,12 +59,23 @@ class DownloadThread(QThread):
for t in self.dlm.threads: for t in self.dlm.threads:
t.join(timeout=5.0) t.join(timeout=5.0)
if t.is_alive(): 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 # 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'), for name, q in zip(
(self.dlm.dl_worker_queue, self.dlm.writer_queue, self.dlm.dl_result_q, (
self.dlm.writer_result_q)): "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}"') logger.debug(f'Cleaning up queue "{name}"')
try: try:
while True: while True:
@ -73,11 +84,11 @@ class DownloadThread(QThread):
q.close() q.close()
q.join_thread() q.join_thread()
except AttributeError: except AttributeError:
logger.warning(f'Queue {name} did not close') logger.warning(f"Queue {name} did not close")
if self.dlm.writer_queue: if self.dlm.writer_queue:
# cancel installation # 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 # forcibly kill DL workers that are not actually dead yet
for child in self.dlm.children: for child in self.dlm.children:
@ -95,9 +106,16 @@ class DownloadThread(QThread):
# force kill any threads that are somehow still alive # force kill any threads that are somehow still alive
for proc in psutil.process_iter(): for proc in psutil.process_iter():
# check whether the process name matches # 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() 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() proc.kill()
logger.info("Download stopped. It can be continued later.") logger.info("Download stopped. It can be continued later.")
@ -111,7 +129,9 @@ class DownloadThread(QThread):
self.dlm.join() self.dlm.join()
except Exception as e: 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)) self.status.emit("error " + str(e))
return return
@ -130,48 +150,65 @@ class DownloadThread(QThread):
dlcs = self.core.get_dlc_for_game(self.igame.app_name) dlcs = self.core.get_dlc_for_game(self.igame.app_name)
if dlcs: if dlcs:
print('The following DLCs are available for this game:') print("The following DLCs are available for this game:")
for dlc in dlcs: for dlc in dlcs:
print(f' - {dlc.app_title} (App name: {dlc.app_name}, version: {dlc.app_version})') print(
print('Manually installing DLCs works the same; just use the DLC app name instead.') 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 # install_dlcs = QMessageBox.question(self, "", "Do you want to install the prequisites", QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes
# TODO # TODO
if game.supports_cloud_saves and not game.is_dlc: 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(
logger.info(f'To download saves for this game run "legendary sync-saves {game.app_name}"') '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) 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 and self.repair and os.path.exists(self.repair_file):
if old_igame.needs_verification: if old_igame.needs_verification:
old_igame.needs_verification = False old_igame.needs_verification = False
self.core.install_game(old_igame) self.core.install_game(old_igame)
logger.debug('Removing repair file.') logger.debug("Removing repair file.")
os.remove(self.repair_file) os.remove(self.repair_file)
if old_igame and old_igame.install_tags != self.igame.install_tags: if old_igame and old_igame.install_tags != self.igame.install_tags:
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.uninstall_tag(old_igame)
self.core.install_game(old_igame) self.core.install_game(old_igame)
self.status.emit("finish") self.status.emit("finish")
def _handle_postinstall(self, postinstall, igame): def _handle_postinstall(self, postinstall, igame):
print('This game lists the following prequisites to be installed:') print("This game lists the following prequisites to be installed:")
print(f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}') print(
f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}'
)
if platform.system() == "Windows": if platform.system() == "Windows":
if QMessageBox.question(self, "", "Do you want to install the prequisites", if (
QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes: QMessageBox.question(
self,
"",
"Do you want to install the prequisites",
QMessageBox.Yes | QMessageBox.No,
)
== QMessageBox.Yes
):
self.core.prereq_installed(igame.app_name) 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) work_dir = os.path.join(igame.install_path, req_path)
fullpath = os.path.join(work_dir, req_exec) 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: else:
self.core.prereq_installed(self.igame.app_name) self.core.prereq_installed(self.igame.app_name)
else: else:
logger.info('Automatic installation not available on Linux.') logger.info("Automatic installation not available on Linux.")
def kill(self): def kill(self):
self._kill = True self._kill = True

View file

@ -58,7 +58,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
self.game_info_tabs.back_clicked.connect(lambda: self.setCurrentIndex(0)) self.game_info_tabs.back_clicked.connect(lambda: self.setCurrentIndex(0))
self.addWidget(self.game_info_tabs) 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.game_info_tabs.info.uninstalled.connect(lambda x: self.setCurrentIndex(0))
self.import_sync_tabs = ImportSyncTabs(self) 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.dl_progress.connect(self.installing_widget.set_status)
self.signals.installation_started.connect(self.installation_started) self.signals.installation_started.connect(self.installation_started)
self.signals.update_gamelist.connect(self.update_list) 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.signals.game_uninstalled.connect(lambda name: self.update_list([name]))
self.game_utils.update_list.connect(self.update_list) 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) self.list_view.layout().addWidget(list_widget)
def update_count_games_label(self): def update_count_games_label(self):
self.count_games_label.setText(self.tr("Installed Games: {} Available Games: {}").format( self.count_games_label.setText(
len(self.core.get_installed_list()), self.tr("Installed Games: {} Available Games: {}").format(
len(self.game_list))) len(self.core.get_installed_list()), len(self.game_list)
)
)
def add_installed_widget(self, app_name): def add_installed_widget(self, app_name):
pixmap = get_pixmap(app_name) pixmap = get_pixmap(app_name)
@ -249,7 +255,10 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
visible = True visible = True
else: else:
visible = True 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 visible = False
w.setVisible(visible) w.setVisible(visible)
@ -261,16 +270,24 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
if widgets := self.widgets.get(app_name): if widgets := self.widgets.get(app_name):
# from update # 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) logger.debug("Update Gamelist: Updated: " + app_name)
igame = self.core.get_installed_game(app_name) igame = self.core.get_installed_game(app_name)
for w in widgets: for w in widgets:
w.igame = igame w.igame = igame
w.update_available = self.core.get_asset(w.game.app_name, w.igame.platform, w.update_available = (
True).build_version != igame.version self.core.get_asset(
w.game.app_name, w.igame.platform, True
).build_version
!= igame.version
)
widgets[0].leaveEvent(None) widgets[0].leaveEvent(None)
# new installed # 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) logger.debug("Update Gamelist: New installed " + app_name)
self.widgets[app_name][0].deleteLater() self.widgets[app_name][0].deleteLater()
self.widgets[app_name][1].deleteLater() self.widgets[app_name][1].deleteLater()
@ -280,8 +297,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
update_list = True update_list = True
# uninstalled # uninstalled
elif not self.core.is_installed(widgets[0].game.app_name) and isinstance(widgets[0], elif not self.core.is_installed(
BaseInstalledWidget): widgets[0].game.app_name
) and isinstance(widgets[0], BaseInstalledWidget):
logger.debug("Update list: Uninstalled: " + app_name) logger.debug("Update list: Uninstalled: " + app_name)
self.widgets[app_name][0].deleteLater() self.widgets[app_name][0].deleteLater()
self.widgets[app_name][1].deleteLater() self.widgets[app_name][1].deleteLater()
@ -305,8 +323,13 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
if not game.app_name in installed_names: if not game.app_name in installed_names:
uninstalled_names.append(game.app_name) uninstalled_names.append(game.app_name)
new_installed_games = list(set(installed_names) - set([i.app_name for i in self.installed])) new_installed_games = list(
new_uninstalled_games = list(set(uninstalled_names) - set([i.app_name for i in self.uninstalled_games])) 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): if (not new_uninstalled_games) and (not new_installed_games):
return return
@ -332,7 +355,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
game = self.core.get_game(name, False) game = self.core.get_game(name, False)
self.add_uninstalled_widget(game) 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] i_widget, list_widget = self.widgets[igame.app_name]
self.icon_view.layout().addWidget(i_widget) self.icon_view.layout().addWidget(i_widget)
@ -348,7 +373,10 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
self.uninstalled_games = [] self.uninstalled_games = []
games, self.dlcs = self.core.get_game_and_dlc_list() games, self.dlcs = self.core.get_game_and_dlc_list()
for game in sorted(games, key=lambda x: x.app_title): 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] i_widget, list_widget = self.widgets[game.app_name]
self.icon_view.layout().addWidget(i_widget) self.icon_view.layout().addWidget(i_widget)
self.list_view.layout().addWidget(list_widget) self.list_view.layout().addWidget(list_widget)
@ -391,8 +419,9 @@ class GamesTab(QStackedWidget, Ui_GamesTab):
self.list_view.setParent(None) self.list_view.setParent(None)
# insert widget in layout # insert widget in layout
self.scroll_widget.layout().insertWidget(1, self.scroll_widget.layout().insertWidget(
self.icon_view if self.head_bar.view.isChecked() else self.list_view) 1, self.icon_view if self.head_bar.view.isChecked() else self.list_view
)
def toggle_view(self): def toggle_view(self):
self.settings.setValue("icon_view", not self.head_bar.view.isChecked()) self.settings.setValue("icon_view", not self.head_bar.view.isChecked())

View file

@ -44,9 +44,15 @@ class SaveWorker(QRunnable):
def run(self) -> None: def run(self) -> None:
try: try:
if isinstance(self.model, DownloadModel): 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: 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: except Exception as e:
self.signals.finished.emit(str(e), self.model.app_name) self.signals.finished.emit(str(e), self.model.app_name)
logger.error(str(e)) logger.error(str(e))
@ -69,7 +75,13 @@ class CloudSaveDialog(QDialog, Ui_SyncSaveDialog):
UPLOAD = 1 UPLOAD = 1
CANCEL = 0 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__() super(CloudSaveDialog, self).__init__()
self.setupUi(self) self.setupUi(self)
@ -155,18 +167,34 @@ class CloudSaveUtils(QObject):
self.core.lgd.set_installed_game(app_name, igame) self.core.lgd.set_installed_game(app_name, igame)
logger.info(f"Set save path of {igame.title} to {savepath}") logger.info(f"Set save path of {igame.title} to {savepath}")
elif not ignore_settings: # sync on startup elif not ignore_settings: # sync on startup
if QMessageBox.question(None, "Warning", self.tr( if (
"Could not compute cloud save path. Please set it in Game settings manually. \nDo you want to launch {} anyway?").format( QMessageBox.question(
igame.title), QMessageBox.Yes | QMessageBox.No, QMessageBox.No) == QMessageBox.Yes: 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 return False
else: else:
raise ValueError("No savepath detected") raise ValueError("No savepath detected")
else: else:
QMessageBox.warning(None, "Warning", QMessageBox.warning(
self.tr("No savepath found. Please set it in Game Settings manually")) None,
"Warning",
self.tr(
"No savepath found. Please set it in Game Settings manually"
),
)
return False 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: if res == SaveGameStatus.NO_SAVE:
return False return False
@ -217,18 +245,27 @@ class CloudSaveUtils(QObject):
self.core.lgd.set_installed_game(app_name, igame) self.core.lgd.set_installed_game(app_name, igame)
logger.info(f"Set save path of {igame.title} to {savepath}") logger.info(f"Set save path of {igame.title} to {savepath}")
else: 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 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: if res == SaveGameStatus.LOCAL_NEWER and not always_ask:
self.upload_saves(igame, dt_local) self.upload_saves(igame, dt_local)
return return
elif res == SaveGameStatus.NO_SAVE and not always_ask: elif res == SaveGameStatus.NO_SAVE and not always_ask:
QMessageBox.warning(None, "No saves", self.tr( QMessageBox.warning(
"There are no saves local and online. Maybe you have to change save path of {}").format(igame.title)) 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) self.sync_finished.emit(app_name)
return return
@ -256,7 +293,11 @@ class CloudSaveUtils(QObject):
def download_saves(self, igame): def download_saves(self, igame):
logger.info("Downloading saves for " + igame.title) 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) w.signals.finished.connect(self.worker_finished)
self.thread_pool.start(w) self.thread_pool.start(w)
@ -266,5 +307,9 @@ class CloudSaveUtils(QObject):
self.sync_finished.emit(app_name) self.sync_finished.emit(app_name)
self.latest_saves = self.get_latest_saves(shared.api_results.saves) self.latest_saves = self.get_latest_saves(shared.api_results.saves)
else: 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) self.sync_finished.emit(app_name)

View file

@ -33,7 +33,10 @@ class GameInfoTabs(SideTabWidget):
self.settings.update_game(app_name) self.settings.update_game(app_name)
# DLC Tab: Disable if no dlcs available # 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) self.setTabEnabled(3, False)
else: else:
self.setTabEnabled(3, True) self.setTabEnabled(3, True)

View file

@ -69,11 +69,18 @@ class GameDlc(QWidget, Ui_GameDlc):
def install(self, app_name): def install(self, app_name):
if not self.core.is_installed(self.game.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( QMessageBox.warning(
self.game.app_title)) self,
"Error",
self.tr("Base Game is not installed. Please install {} first").format(
self.game.app_title
),
)
return 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): class GameDlcWidget(QFrame, Ui_GameDlcWidget):
@ -109,10 +116,15 @@ class GameDlcWidget(QFrame, Ui_GameDlcWidget):
self.pixmap = a0 self.pixmap = a0
self.image.setPixmap( self.image.setPixmap(
self.pixmap.scaledToHeight( self.pixmap.scaledToHeight(
self.dlc_info.size().height() - (self.image.contentsMargins().top() + self.dlc_info.size().height()
self.image.contentsMargins().bottom() + - (
self.image.lineWidth()*2), self.image.contentsMargins().top()
Qt.SmoothTransformation)) + self.image.contentsMargins().bottom()
+ self.image.lineWidth() * 2
),
Qt.SmoothTransformation,
)
)
self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) self.image.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
def uninstall_dlc(self): def uninstall_dlc(self):

View file

@ -58,20 +58,32 @@ class GameInfo(QWidget, Ui_GameInfo):
self.uninstalled.emit(self.game.app_name) self.uninstalled.emit(self.game.app_name)
def repair(self): 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): if not os.path.exists(repair_file):
QMessageBox.warning(self, "Warning", self.tr( QMessageBox.warning(
"Repair file does not exist or game does not need a repair. Please verify game first")) self,
"Warning",
self.tr(
"Repair file does not exist or game does not need a repair. Please verify game first"
),
)
return return
self.signals.install_game.emit(InstallOptionsModel(app_name=self.game.app_name, repair=True, self.signals.install_game.emit(
update=True)) InstallOptionsModel(app_name=self.game.app_name, repair=True, update=True)
)
def verify(self): def verify(self):
if not os.path.exists(self.igame.install_path): if not os.path.exists(self.igame.install_path):
logger.error("Path does not exist") logger.error("Path does not exist")
QMessageBox.warning(self, "Warning", QMessageBox.warning(
self.tr("Installation path of {} does not exist. Cannot verify").format( self,
self.igame.title)) "Warning",
self.tr("Installation path of {} does not exist. Cannot verify").format(
self.igame.title
),
)
return return
self.verify_widget.setCurrentIndex(1) self.verify_widget.setCurrentIndex(1)
verify_worker = VerifyWorker(self.core, self.game.app_name) 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): def finish_verify(self, failed, missing, app_name):
if failed == missing == 0: if failed == missing == 0:
QMessageBox.information(self, "Summary", QMessageBox.information(
"Game was verified successfully. No missing or corrupt files found") self,
"Summary",
"Game was verified successfully. No missing or corrupt files found",
)
igame = self.core.get_installed_game(app_name) igame = self.core.get_installed_game(app_name)
if igame.needs_verification: if igame.needs_verification:
igame.needs_verification = False igame.needs_verification = False
@ -99,19 +114,28 @@ class GameInfo(QWidget, Ui_GameInfo):
QMessageBox.warning(self, "Warning", self.tr("Something went wrong")) QMessageBox.warning(self, "Warning", self.tr("Something went wrong"))
else: else:
ans = QMessageBox.question(self, "Summary", self.tr( ans = QMessageBox.question(
'Verification failed, {} file(s) corrupted, {} file(s) are missing. Do you want to repair them?').format( self,
failed, missing), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes) "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: if ans == QMessageBox.Yes:
self.signals.install_game.emit(InstallOptionsModel(app_name=self.game.app_name, repair=True, self.signals.install_game.emit(
update=True)) InstallOptionsModel(
app_name=self.game.app_name, repair=True, update=True
)
)
self.verify_widget.setCurrentIndex(0) self.verify_widget.setCurrentIndex(0)
self.verify_threads.pop(app_name) self.verify_threads.pop(app_name)
def update_game(self, app_name: str): def update_game(self, app_name: str):
self.game = self.core.get_game(app_name) self.game = self.core.get_game(app_name)
self.igame = self.core.get_installed_game(self.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) pixmap = get_pixmap(self.game.app_name)
w = 200 w = 200
@ -122,7 +146,9 @@ class GameInfo(QWidget, Ui_GameInfo):
if self.igame: if self.igame:
self.version.setText(self.igame.version) self.version.setText(self.igame.version)
else: 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"]) self.dev.setText(self.game.metadata["developer"])
if self.igame: if self.igame:
@ -154,10 +180,14 @@ class GameInfo(QWidget, Ui_GameInfo):
self.steam_worker.set_app_name(self.game.app_name) self.steam_worker.set_app_name(self.game.app_name)
QThreadPool.globalInstance().start(self.steam_worker) 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) self.verify_widget.setCurrentIndex(0)
elif self.verify_threads.get(self.game.app_name): elif self.verify_threads.get(self.game.app_name):
self.verify_widget.setCurrentIndex(1) self.verify_widget.setCurrentIndex(1)
self.verify_progress.setValue( 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
) )

View file

@ -4,7 +4,14 @@ from logging import getLogger
from typing import Tuple from typing import Tuple
from PyQt5.QtCore import QSettings, QThreadPool, Qt 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 qtawesome import icon
from legendary.core import LegendaryCore from legendary.core import LegendaryCore
@ -23,7 +30,7 @@ def find_proton_wrappers():
os.path.expanduser("~/.steam/steam/steamapps/common"), os.path.expanduser("~/.steam/steam/steamapps/common"),
"/usr/share/steam/compatibilitytools.d", "/usr/share/steam/compatibilitytools.d",
os.path.expanduser("~/.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: for c in compatibilitytools_dirs:
if os.path.exists(c): if os.path.exists(c):
@ -31,7 +38,9 @@ def find_proton_wrappers():
proton = os.path.join(c, i, "proton") proton = os.path.join(c, i, "proton")
compatibilitytool = os.path.join(c, i, "compatibilitytool.vdf") compatibilitytool = os.path.join(c, i, "compatibilitytool.vdf")
toolmanifest = os.path.join(c, i, "toolmanifest.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' wrapper = '"' + proton + '" run'
possible_proton_wrappers.append(wrapper) possible_proton_wrappers.append(wrapper)
if not possible_proton_wrappers: if not possible_proton_wrappers:
@ -53,14 +62,23 @@ class GameSettings(QWidget, Ui_GameSettings):
self.core = core self.core = core
self.settings = QSettings() self.settings = QSettings()
self.cloud_save_path_edit = PathEdit("", file_type=QFileDialog.DirectoryOnly, self.cloud_save_path_edit = PathEdit(
ph_text=self.tr("Cloud save path"), "",
edit_func=lambda text: (os.path.exists(text), text), file_type=QFileDialog.DirectoryOnly,
save_func=self.save_save_path) ph_text=self.tr("Cloud save path"),
self.cloud_gb.layout().addRow(QLabel(self.tr("Save path")), self.cloud_save_path_edit) 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 = QPushButton(
self.compute_save_path_button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed) 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.compute_save_path_button.clicked.connect(self.compute_save_path)
self.cloud_gb.layout().addRow(None, self.compute_save_path_button) 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") lambda x: self.update_combobox(x, "skip_update_check")
) )
self.cloud_sync.stateChanged.connect( 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( self.launch_params.textChanged.connect(
lambda x: self.save_line_edit("start_params", x) lambda x: self.save_line_edit("start_params", x)
) )
self.wrapper.textChanged.connect( self.wrapper.textChanged.connect(lambda x: self.save_line_edit("wrapper", x))
lambda x: self.save_line_edit("wrapper", x)
)
self.override_exe_edit.textChanged.connect( self.override_exe_edit.textChanged.connect(
lambda x: self.save_line_edit("override_exe", x) lambda x: self.save_line_edit("override_exe", x)
) )
@ -92,7 +110,7 @@ class GameSettings(QWidget, Ui_GameSettings):
self.proton_prefix = PathEdit( self.proton_prefix = PathEdit(
file_type=QFileDialog.DirectoryOnly, file_type=QFileDialog.DirectoryOnly,
edit_func=self.proton_prefix_edit, 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) 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: Remove the spacerItem and margins from the linux settings
# FIXME: This should be handled differently at soem point in the future # FIXME: This should be handled differently at soem point in the future
self.linux_settings.layout().setContentsMargins(0, 0, 0, 0) self.linux_settings.layout().setContentsMargins(0, 0, 0, 0)
for item in [self.linux_settings.layout().itemAt(idx) for idx in for item in [
range(self.linux_settings.layout().count())]: self.linux_settings.layout().itemAt(idx)
for idx in range(self.linux_settings.layout().count())
]:
if item.spacerItem(): if item.spacerItem():
self.linux_settings.layout().removeItem(item) self.linux_settings.layout().removeItem(item)
del item del item
@ -113,7 +133,10 @@ class GameSettings(QWidget, Ui_GameSettings):
self.game_settings_layout.setAlignment(Qt.AlignTop) self.game_settings_layout.setAlignment(Qt.AlignTop)
def compute_save_path(self): 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: try:
new_path = self.core.get_save_path(self.game.app_name) new_path = self.core.get_save_path(self.game.app_name)
except Exception as e: 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.setText(self.tr("Loading"))
self.cloud_save_path_edit.setDisabled(True) self.cloud_save_path_edit.setDisabled(True)
self.compute_save_path_button.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[:] 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) QThreadPool.globalInstance().start(resolver)
return return
else: else:
self.cloud_save_path_edit.setText(new_path) self.cloud_save_path_edit.setText(new_path)
def wine_resolver_finished(self, path, app_name): 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: if app_name == self.game.app_name:
self.cloud_save_path_edit.setDisabled(False) self.cloud_save_path_edit.setDisabled(False)
self.compute_save_path_button.setDisabled(False) self.compute_save_path_button.setDisabled(False)
@ -139,9 +168,13 @@ class GameSettings(QWidget, Ui_GameSettings):
os.makedirs(path) os.makedirs(path)
except PermissionError: except PermissionError:
self.cloud_save_path_edit.setText("") self.cloud_save_path_edit.setText("")
QMessageBox.warning(None, "Error", self.tr( QMessageBox.warning(
"Error while launching {}. No permission to create {}").format( None,
self.game.app_title, path)) "Error",
self.tr(
"Error while launching {}. No permission to create {}"
).format(self.game.app_title, path),
)
return return
if not path: if not path:
self.cloud_save_path_edit.setText("") 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.add_section(self.game.app_name)
self.core.lgd.config.set(self.game.app_name, option, value) self.core.lgd.config.set(self.game.app_name, option, value)
else: else:
if self.core.lgd.config.has_section(self.game.app_name) and self.core.lgd.config.get( if (
f"{self.game.app_name}", option, fallback=None) is not None: 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) self.core.lgd.config.remove_option(self.game.app_name, option)
if not self.core.lgd.config[self.game.app_name]: if not self.core.lgd.config[self.game.app_name]:
self.core.lgd.config.remove_section(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") self.core.lgd.config.set(self.game.app_name, option, "false")
else: else:
if self.game.app_name in self.core.lgd.config.sections(): 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) self.core.lgd.config.remove_option(self.game.app_name, option)
if not self.core.lgd.config[self.game.app_name]: if not self.core.lgd.config[self.game.app_name]:
self.core.lgd.config.remove_section(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 # Dont use Proton
if i == 0: if i == 0:
if f"{self.game.app_name}" in self.core.lgd.config.sections(): 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): if self.core.lgd.config.get(
self.core.lgd.config.remove_option(self.game.app_name, "wrapper") f"{self.game.app_name}", "wrapper", fallback=False
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") 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]: if not self.core.lgd.config[self.game.app_name]:
self.core.lgd.config.remove_section(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 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): if self.core.lgd.config.get(
self.core.lgd.config.remove_option(f"{self.game.app_name}.env", "STEAM_COMPAT_DATA_PATH") 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"]: if not self.core.lgd.config[self.game.app_name + ".env"]:
self.core.lgd.config.remove_section(self.game.app_name + ".env") self.core.lgd.config.remove_section(self.game.app_name + ".env")
self.proton_prefix.setEnabled(False) self.proton_prefix.setEnabled(False)
# lk: TODO: This has to be fixed properly. # 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 # 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.wrapper.setEnabled(True)
self.linux_settings.wine_groupbox.setEnabled(True) self.linux_settings.wine_groupbox.setEnabled(True)
else: else:
@ -225,8 +283,11 @@ class GameSettings(QWidget, Ui_GameSettings):
self.core.lgd.config[self.game.app_name + ".env"] = {} 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, "wrapper", wrapper)
self.core.lgd.config.set(self.game.app_name, "no_wine", "true") 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", self.core.lgd.config.set(
os.path.expanduser("~/.proton")) self.game.app_name + ".env",
"STEAM_COMPAT_DATA_PATH",
os.path.expanduser("~/.proton"),
)
self.proton_prefix.setText(os.path.expanduser("~/.proton")) self.proton_prefix.setText(os.path.expanduser("~/.proton"))
# Dont use Wine # Dont use Wine
@ -243,7 +304,9 @@ class GameSettings(QWidget, Ui_GameSettings):
return os.path.exists(parent), text return os.path.exists(parent), text
def proton_prefix_save(self, text: str): 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() self.core.lgd.save_config()
def update_game(self, app_name: str): 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) self.igame = self.core.get_installed_game(self.game.app_name)
if self.igame: if self.igame:
if self.igame.can_run_offline: 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": if offline == "true":
self.offline.setCurrentIndex(1) self.offline.setCurrentIndex(1)
elif offline == "false": elif offline == "false":
@ -266,7 +331,9 @@ class GameSettings(QWidget, Ui_GameSettings):
else: else:
self.offline.setEnabled(False) 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": if skip_update == "true":
self.skip_update.setCurrentIndex(1) self.skip_update.setCurrentIndex(1)
elif skip_update == "false": elif skip_update == "false":
@ -286,13 +353,19 @@ class GameSettings(QWidget, Ui_GameSettings):
else: else:
self.linux_settings_scroll.setVisible(True) 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: if proton and "proton" in proton:
self.proton_prefix.setEnabled(True) self.proton_prefix.setEnabled(True)
self.proton_wrapper.setCurrentText(f'"{proton.replace(" run", "")}" run') self.proton_wrapper.setCurrentText(
proton_prefix = self.core.lgd.config.get(f"{app_name}.env", "STEAM_COMPAT_DATA_PATH", f'"{proton.replace(" run", "")}" run'
fallback=self.tr( )
"Please select path for proton prefix")) 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.proton_prefix.setText(proton_prefix)
self.wrapper.setEnabled(False) self.wrapper.setEnabled(False)
else: else:
@ -305,15 +378,21 @@ class GameSettings(QWidget, Ui_GameSettings):
self.cloud_save_path_edit.setText("") self.cloud_save_path_edit.setText("")
else: else:
self.cloud_gb.setEnabled(True) 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) self.cloud_sync.setChecked(sync_cloud)
if self.igame.save_path: if self.igame.save_path:
self.cloud_save_path_edit.setText(self.igame.save_path) self.cloud_save_path_edit.setText(self.igame.save_path)
else: else:
self.cloud_save_path_edit.setText("") self.cloud_save_path_edit.setText("")
self.launch_params.setText(self.core.lgd.config.get(self.game.app_name, "start_params", fallback="")) self.launch_params.setText(
self.override_exe_edit.setText(self.core.lgd.config.get(self.game.app_name, "override_exe", fallback="")) 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 self.change = True

View file

@ -56,10 +56,15 @@ class GameUtils(QObject):
game = self.core.get_game(app_name) game = self.core.get_game(app_name)
igame = self.core.get_installed_game(app_name) igame = self.core.get_installed_game(app_name)
if not os.path.exists(igame.install_path): if not os.path.exists(igame.install_path):
if QMessageBox.Yes == QMessageBox.question(None, "Uninstall", self.tr( if QMessageBox.Yes == QMessageBox.question(
"Game files of {} do not exist. Remove it from installed games?").format(igame.title), None,
QMessageBox.Yes | QMessageBox.No, "Uninstall",
QMessageBox.Yes): 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) self.core.lgd.remove_installed_game(app_name)
return True return True
else: else:
@ -72,7 +77,9 @@ class GameUtils(QObject):
shared.signals.game_uninstalled.emit(app_name) shared.signals.game_uninstalled.emit(app_name)
return True 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) game = self.core.get_game(app_name)
dont_sync_after_finish = False dont_sync_after_finish = False
@ -90,10 +97,19 @@ class GameUtils(QObject):
return return
self.sync_finished(app_name) 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, def launch_game(
wine_pfx: str = None, ask_always_sync: bool = False): 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: if shared.args.offline:
offline = True offline = True
game = self.core.get_game(app_name) game = self.core.get_game(app_name)
@ -105,8 +121,15 @@ class GameUtils(QObject):
return return
if QSettings().value("confirm_start", False, bool): if QSettings().value("confirm_start", False, bool):
if not QMessageBox.question(None, "Launch", self.tr("Do you want to launch {}").format(game.app_title), if (
QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes: 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") logger.info("Cancel Startup")
self.finished.emit(app_name, "") self.finished.emit(app_name, "")
return return
@ -119,12 +142,18 @@ class GameUtils(QObject):
logger.error(f"{app_name} is not installed") logger.error(f"{app_name} is not installed")
if game.is_dlc: if game.is_dlc:
logger.error("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 return
if not os.path.exists(igame.install_path): if not os.path.exists(igame.install_path):
logger.error("Game doesn't exist") logger.error("Game doesn't exist")
self.finished.emit(app_name, self.finished.emit(
self.tr("Game files of {} do not exist. Please install game").format(game.app_title)) app_name,
self.tr(
"Game files of {} do not exist. Please install game"
).format(game.app_title),
)
return return
process = GameProcess(app_name) 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): if not skip_update_check and not self.core.is_noupdate_game(app_name):
# check updates # check updates
try: 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: except ValueError:
self.finished.emit(app_name, self.tr("Metadata doesn't exist")) self.finished.emit(app_name, self.tr("Metadata doesn't exist"))
return return
@ -144,12 +175,15 @@ class GameUtils(QObject):
self.finished.emit(app_name, self.tr("Please update game")) self.finished.emit(app_name, self.tr("Please update game"))
return return
params: LaunchParameters = self.core.get_launch_parameters(app_name=app_name, offline=offline, params: LaunchParameters = self.core.get_launch_parameters(
wine_bin=wine_bin, wine_pfx=wine_pfx) app_name=app_name, offline=offline, wine_bin=wine_bin, wine_pfx=wine_pfx
)
full_params = list() full_params = list()
full_params.extend(params.launch_command) 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.game_parameters)
full_params.extend(params.egl_parameters) full_params.extend(params.egl_parameters)
full_params.extend(params.user_parameters) full_params.extend(params.user_parameters)
@ -170,23 +204,33 @@ class GameUtils(QObject):
os.makedirs(val) os.makedirs(val)
except PermissionError as e: except PermissionError as e:
logger.error(str(e)) logger.error(str(e))
QMessageBox.warning(None, "Error", QMessageBox.warning(
self.tr( None,
"Error while launching {}. No permission to create {} for {}").format( "Error",
game.app_title, val, env)) self.tr(
"Error while launching {}. No permission to create {} for {}"
).format(game.app_title, val, env),
)
process.deleteLater() process.deleteLater()
return return
# check wine executable # check wine executable
if shutil.which(full_params[0]) is None: if shutil.which(full_params[0]) is None:
# wine binary does not exist # wine binary does not exist
QMessageBox.warning(None, "Warning", self.tr( QMessageBox.warning(
"Wine executable '{}' does not exist. Please change it in Settings").format(full_params[0])) None,
"Warning",
self.tr(
"Wine executable '{}' does not exist. Please change it in Settings"
).format(full_params[0]),
)
process.deleteLater() process.deleteLater()
return return
process.setProcessEnvironment(environment) process.setProcessEnvironment(environment)
process.game_finished.connect(self.game_finished) 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:]) process.start(full_params[0], full_params[1:])
self.game_launched.emit(app_name) self.game_launched.emit(app_name)
@ -201,25 +245,37 @@ class GameUtils(QObject):
webbrowser.open(origin_uri) webbrowser.open(origin_uri)
self.finished.emit(app_name, "") self.finished.emit(app_name, "")
return return
wine_pfx = self.core.lgd.config.get(game.app_name, 'wine_prefix', wine_pfx = self.core.lgd.config.get(
fallback=os.path.expanduser("~/.wine")) game.app_name, "wine_prefix", fallback=os.path.expanduser("~/.wine")
)
if not wine_bin: 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: if shutil.which(wine_bin) is None:
# wine binary does not exist # wine binary does not exist
QMessageBox.warning(None, "Warning", QMessageBox.warning(
self.tr("Wine executable '{}' does not exist. Please change it in Settings").format( None,
wine_bin)) "Warning",
self.tr(
"Wine executable '{}' does not exist. Please change it in Settings"
).format(wine_bin),
)
process.deleteLater() process.deleteLater()
return return
env = self.core.get_app_environment(game.app_name, wine_pfx=wine_pfx) 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"): 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 ' logger.error(
f'to use in the configuration file or command line. See the README for details.') f"In order to launch Origin correctly you must specify the wine binary and prefix "
self.finished.emit(app_name, self.tr("No wine executable selected. Please set it in settings")) 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 return
environment = QProcessEnvironment() environment = QProcessEnvironment()
@ -231,17 +287,27 @@ class GameUtils(QObject):
if QSettings().value("show_console", False, bool): if QSettings().value("show_console", False, bool):
self.console.show() self.console.show()
process.readyReadStandardOutput.connect(lambda: self.console.log( process.readyReadStandardOutput.connect(
str(process.readAllStandardOutput().data(), "utf-8", "ignore"))) lambda: self.console.log(
process.readyReadStandardError.connect(lambda: self.console.error( str(process.readAllStandardOutput().data(), "utf-8", "ignore")
str(process.readAllStandardError().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): def game_finished(self, exit_code, app_name):
logger.info("Game exited with exit code: " + str(exit_code)) logger.info("Game exited with exit code: " + str(exit_code))
is_origin = self.core.get_game(app_name).third_party_store == "Origin" is_origin = self.core.get_game(app_name).third_party_store == "Origin"
if exit_code == 53 and is_origin: if exit_code == 53 and is_origin:
msg_box = QMessageBox() 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("Download"), QMessageBox.YesRole)
msg_box.addButton(QPushButton("Cancel"), QMessageBox.RejectRole) msg_box.addButton(QPushButton("Cancel"), QMessageBox.RejectRole)
resp = msg_box.exec() resp = msg_box.exec()
@ -249,8 +315,13 @@ class GameUtils(QObject):
if resp == 0: if resp == 0:
webbrowser.open("https://www.dm.origin.com/download") webbrowser.open("https://www.dm.origin.com/download")
if is_origin and exit_code == 1: if is_origin and exit_code == 1:
QMessageBox.warning(None, "Warning", QMessageBox.warning(
self.tr("Failed to launch {}").format(self.core.get_game(app_name).app_title)) 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) game: RunningGameModel = self.running_games.get(app_name, None)
if app_name in self.running_games.keys(): 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 self.core.get_game(app_name).supports_cloud_saves:
if exit_code != 0: if exit_code != 0:
r = QMessageBox.question(None, "Question", self.tr( r = QMessageBox.question(
"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( None,
exit_code), "Question",
buttons=QMessageBox.Yes | QMessageBox.No, defaultButton=QMessageBox.Yes) 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: if r != QMessageBox.Yes:
return return
self.cloud_save_utils.game_finished(app_name, game.always_ask_sync) self.cloud_save_utils.game_finished(app_name, game.always_ask_sync)

View file

@ -33,27 +33,30 @@ class BaseInstalledWidget(QGroupBox):
"update_available": self.tr("Start game without version check"), "update_available": self.tr("Start game without version check"),
"launch": self.tr("Launch Game"), "launch": self.tr("Launch Game"),
"launch_origin": self.tr("Launch/Link"), "launch_origin": self.tr("Launch/Link"),
"running": self.tr("Game running") "running": self.tr("Game running"),
}, },
"default": { "default": {
"running": self.tr("Game running"), "running": self.tr("Game running"),
"syncing": self.tr("Syncing cloud saves"), "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.game = self.core.get_game(app_name)
self.igame = self.core.get_installed_game(app_name) # None if origin self.igame = self.core.get_installed_game(app_name) # None if origin
self.image = QLabel() 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.game_running = False
self.offline = shared.args.offline self.offline = shared.args.offline
self.update_available = False self.update_available = False
if self.igame and self.core.lgd.assets: if self.igame and self.core.lgd.assets:
try: try:
remote_version = self.core.get_asset(self.game.app_name, platform=self.igame.platform, remote_version = self.core.get_asset(
update=False).build_version self.game.app_name, platform=self.igame.platform, update=False
).build_version
except ValueError: except ValueError:
logger.error("Asset error for " + self.game.app_title) logger.error("Asset error for " + self.game.app_title)
self.update_available = False self.update_available = False
@ -75,19 +78,26 @@ class BaseInstalledWidget(QGroupBox):
sync.triggered.connect(self.sync_game) sync.triggered.connect(self.sync_game)
self.addAction(sync) self.addAction(sync)
if os.path.exists(os.path.expanduser(f"~/Desktop/{self.game.app_title}.desktop")) \ if os.path.exists(
or os.path.exists(os.path.expanduser(f"~/Desktop/{self.game.app_title}.lnk")): 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")) self.create_desktop = QAction(self.tr("Remove Desktop link"))
else: else:
self.create_desktop = QAction(self.tr("Create Desktop link")) self.create_desktop = QAction(self.tr("Create Desktop link"))
if self.igame: 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) self.addAction(self.create_desktop)
if platform.system() == "Linux": 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": 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: else:
start_menu_file = "" start_menu_file = ""
if platform.system() in ["Windows", "Linux"]: if platform.system() in ["Windows", "Linux"]:
@ -96,7 +106,9 @@ class BaseInstalledWidget(QGroupBox):
else: else:
self.create_start_menu = QAction(self.tr("Create start menu link")) self.create_start_menu = QAction(self.tr("Create start menu link"))
if self.igame: 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) self.addAction(self.create_start_menu)
reload_image = QAction(self.tr("Reload Image"), self) reload_image = QAction(self.tr("Reload Image"), self)
@ -105,19 +117,26 @@ class BaseInstalledWidget(QGroupBox):
uninstall = QAction(self.tr("Uninstall"), self) uninstall = QAction(self.tr("Uninstall"), self)
uninstall.triggered.connect( uninstall.triggered.connect(
lambda: shared.signals.update_gamelist.emit([self.game.app_name]) if self.game_utils.uninstall_game( lambda: shared.signals.update_gamelist.emit([self.game.app_name])
self.game.app_name) else None) if self.game_utils.uninstall_game(self.game.app_name)
else None
)
self.addAction(uninstall) self.addAction(uninstall)
def reload_image(self): def reload_image(self):
utils.download_image(self.game, True) utils.download_image(self.game, True)
pm = utils.get_pixmap(self.game.app_name) 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): def create_desktop_link(self, type_of_link):
if platform.system() not in ["Windows", "Linux"]: if platform.system() not in ["Windows", "Linux"]:
QMessageBox.warning(self, "Warning", QMessageBox.warning(
f"Create a Desktop link is currently not supported on {platform.system()}") self,
"Warning",
f"Create a Desktop link is currently not supported on {platform.system()}",
)
return return
if type_of_link == "desktop": if type_of_link == "desktop":
path = os.path.expanduser(f"~/Desktop/") path = os.path.expanduser(f"~/Desktop/")
@ -125,19 +144,25 @@ class BaseInstalledWidget(QGroupBox):
path = os.path.expanduser("~/.local/share/applications/") path = os.path.expanduser("~/.local/share/applications/")
else: else:
return return
if not (os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.desktop")) if not (
or os.path.exists(os.path.expanduser(f"{path}{self.game.app_title}.lnk"))): 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: try:
if not create_desktop_link(self.game.app_name, self.core, type_of_link): if not create_desktop_link(self.game.app_name, self.core, type_of_link):
return return
except PermissionError: 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": if type_of_link == "desktop":
self.create_desktop.setText(self.tr("Remove Desktop link")) self.create_desktop.setText(self.tr("Remove Desktop link"))
elif type_of_link == "start_menu": elif type_of_link == "start_menu":
self.create_start_menu.setText(self.tr("Remove Start menu link")) self.create_start_menu.setText(self.tr("Remove Start menu link"))
else: 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")) 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")): 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")) 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 not self.game_running:
if self.game.supports_cloud_saves: if self.game.supports_cloud_saves:
self.syncing_cloud_saves = True 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): def sync_finished(self, app_name):
self.syncing_cloud_saves = False self.syncing_cloud_saves = False
def sync_game(self): 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 self.syncing_cloud_saves = True
def game_finished(self, app_name, error): def game_finished(self, app_name, error):

View file

@ -17,7 +17,9 @@ class BaseUninstalledWidget(QGroupBox):
self.game = game self.game = game
self.core = core self.core = core
self.image = QLabel() 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.installing = False
self.setContextMenuPolicy(Qt.ActionsContextMenu) self.setContextMenuPolicy(Qt.ActionsContextMenu)
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)
@ -29,7 +31,9 @@ class BaseUninstalledWidget(QGroupBox):
def reload_image(self): def reload_image(self):
utils.download_image(self.game, True) utils.download_image(self.game, True)
pm = utils.get_uninstalled_pixmap(self.game.app_name) 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): def install(self):
self.show_uninstalled_info.emit(self.game) self.show_uninstalled_info.emit(self.game)

View file

@ -6,7 +6,9 @@ from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QPushButton, QLabel
from qtawesome import icon from qtawesome import icon
from rare import shared 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") logger = getLogger("GameWidgetInstalled")
@ -43,7 +45,9 @@ class InstalledIconWidget(BaseInstalledWidget):
self.menu_btn.setIcon(icon("ei.info-circle")) self.menu_btn.setIcon(icon("ei.info-circle"))
# self.menu_btn.setObjectName("installed_menu_button") # self.menu_btn.setObjectName("installed_menu_button")
self.menu_btn.setIconSize(QSize(18, 18)) 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) self.menu_btn.leaveEvent = lambda x: self.enterEvent(None)
# remove Border # remove Border
@ -77,7 +81,9 @@ class InstalledIconWidget(BaseInstalledWidget):
elif self.update_available: elif self.update_available:
self.info_label.setText(self.texts["hover"]["update_available"]) self.info_label.setText(self.texts["hover"]["update_available"])
else: 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: def leaveEvent(self, a0: QEvent = None) -> None:
if self.game_running: if self.game_running:

View file

@ -4,7 +4,9 @@ from PyQt5.QtCore import QProcess, pyqtSignal, Qt
from PyQt5.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout from PyQt5.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout
from qtawesome import icon 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 from rare.utils.utils import get_size
logger = getLogger("GameWidget") logger = getLogger("GameWidget")
@ -36,7 +38,9 @@ class InstalledListWidget(BaseInstalledWidget):
self.title_label.setWordWrap(True) self.title_label.setWordWrap(True)
self.childLayout.addWidget(self.title_label) self.childLayout.addWidget(self.title_label)
self.app_name_label = QLabel(self.game.app_name) 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.setObjectName("launch_game_button")
self.launch_button.setFixedWidth(150) self.launch_button.setFixedWidth(150)
@ -57,7 +61,9 @@ class InstalledListWidget(BaseInstalledWidget):
self.childLayout.addWidget(self.developer_label) self.childLayout.addWidget(self.developer_label)
if self.igame: if self.igame:
self.version_label = QLabel("Version: " + str(self.igame.version)) 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.version_label)
self.childLayout.addWidget(self.size_label) self.childLayout.addWidget(self.size_label)

View file

@ -3,7 +3,12 @@ from PyQt5.QtGui import QPaintEvent, QPainter, QPixmap, QPen, QFont, QColor
from PyQt5.QtWidgets import QVBoxLayout, QLabel, QHBoxLayout, QWidget from PyQt5.QtWidgets import QVBoxLayout, QLabel, QHBoxLayout, QWidget
from rare import shared 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): class InstallingGameWidget(QWidget):
@ -53,17 +58,23 @@ class PaintWidget(QWidget):
game = shared.core.get_game(app_name, False) game = shared.core.get_game(app_name, False)
self.color_image = get_pixmap(game.app_name) self.color_image = get_pixmap(game.app_name)
w = 200 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.setFixedSize(self.color_image.size())
self.bw_image = get_uninstalled_pixmap(app_name) 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 self.progress = 0
pixel_list = [] pixel_list = []
for x in range(self.color_image.width()): for x in range(self.color_image.width()):
for y in range(self.color_image.height()): for y in range(self.color_image.height()):
# convert pixmap to qimage, get pixel and remove alpha channel # 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) self.rgb_tuple = optimal_text_background(pixel_list)
@ -74,14 +85,21 @@ class PaintWidget(QWidget):
w = self.bw_image.width() * self.progress // 100 w = self.bw_image.width() * self.progress // 100
painter.drawPixmap(0, 0, w, self.color_image.height(), painter.drawPixmap(
self.color_image.copy(QRect(0, 0, w, self.color_image.height()))) 0,
0,
w,
self.color_image.height(),
self.color_image.copy(QRect(0, 0, w, self.color_image.height())),
)
# Draw Circle # Draw Circle
pen = QPen(QColor(*self.rgb_tuple), 3) pen = QPen(QColor(*self.rgb_tuple), 3)
painter.setPen(pen) painter.setPen(pen)
painter.setBrush(QColor(*self.rgb_tuple)) 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 # draw text
painter.setPen(QColor(*text_color_for_background(self.rgb_tuple))) painter.setPen(QColor(*text_color_for_background(self.rgb_tuple)))

View file

@ -4,13 +4,14 @@ from PyQt5.QtWidgets import QVBoxLayout, QLabel
from legendary.core import LegendaryCore from legendary.core import LegendaryCore
from legendary.models.game import Game 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") logger = getLogger("Uninstalled")
class IconWidgetUninstalled(BaseUninstalledWidget): class IconWidgetUninstalled(BaseUninstalledWidget):
def __init__(self, game: Game, core: LegendaryCore, pixmap): def __init__(self, game: Game, core: LegendaryCore, pixmap):
super(IconWidgetUninstalled, self).__init__(game, core, pixmap) super(IconWidgetUninstalled, self).__init__(game, core, pixmap)
self.layout = QVBoxLayout() self.layout = QVBoxLayout()

View file

@ -4,13 +4,14 @@ from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QLabel, QHBoxLayout, QVBoxLayout, QPushButton from PyQt5.QtWidgets import QLabel, QHBoxLayout, QVBoxLayout, QPushButton
from legendary.core import LegendaryCore 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") logger = getLogger("Game")
class ListWidgetUninstalled(BaseUninstalledWidget): class ListWidgetUninstalled(BaseUninstalledWidget):
def __init__(self, core: LegendaryCore, game, pixmap): def __init__(self, core: LegendaryCore, game, pixmap):
super(ListWidgetUninstalled, self).__init__(game, core, pixmap) super(ListWidgetUninstalled, self).__init__(game, core, pixmap)
self.layout = QHBoxLayout() self.layout = QHBoxLayout()

View file

@ -1,5 +1,12 @@
from PyQt5.QtCore import QSize, QSettings, pyqtSignal 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 qtawesome import icon
from rare.utils.extra_widgets import SelectViewWidget from rare.utils.extra_widgets import SelectViewWidget
@ -17,15 +24,26 @@ class GameListHeadBar(QWidget):
# self.layout.addWidget(self.installed_only) # self.layout.addWidget(self.installed_only)
self.filter = QComboBox() self.filter = QComboBox()
self.filter.addItems([self.tr("All"), self.filter.addItems(
self.tr("Installed only"), [
self.tr("Offline Games"), self.tr("All"),
self.tr("32 Bit Games"), self.tr("Installed only"),
self.tr("Mac games"), self.tr("Offline Games"),
self.tr("Exclude Origin")]) self.tr("32 Bit Games"),
self.tr("Mac games"),
self.tr("Exclude Origin"),
]
)
self.layout().addWidget(self.filter) self.layout().addWidget(self.filter)
self.available_filters = ["all", "installed", "offline", "32bit", "mac", "installable"] self.available_filters = [
"all",
"installed",
"offline",
"32bit",
"mac",
"installable",
]
try: try:
self.filter.setCurrentIndex(self.settings.value("filter", 0, int)) self.filter.setCurrentIndex(self.settings.value("filter", 0, int))

View file

@ -6,22 +6,21 @@ from .import_group import ImportGroup
class ImportSyncTabs(SideTabWidget): class ImportSyncTabs(SideTabWidget):
def __init__(self, parent=None): def __init__(self, parent=None):
super(ImportSyncTabs, self).__init__(show_back=True, parent=parent) super(ImportSyncTabs, self).__init__(show_back=True, parent=parent)
self.import_widget = ImportSyncWidget( self.import_widget = ImportSyncWidget(
ImportGroup(self), ImportGroup(self),
self.tr('Import Game'), self.tr("Import Game"),
self.tr('To import games from Epic Games Store, please enable EGL Sync.'), self.tr("To import games from Epic Games Store, please enable EGL Sync."),
self self,
) )
self.addTab(self.import_widget, self.tr("Import Games")) self.addTab(self.import_widget, self.tr("Import Games"))
self.egl_sync_widget = ImportSyncWidget( self.egl_sync_widget = ImportSyncWidget(
EGLSyncGroup(self), EGLSyncGroup(self),
self.tr('Sync with EGL'), self.tr("Sync with EGL"),
self.tr('To import EGL games from directories, please use Import Game.'), self.tr("To import EGL games from directories, please use Import Game."),
self self,
) )
self.addTab(self.egl_sync_widget, self.tr("Sync with EGL")) self.addTab(self.egl_sync_widget, self.tr("Sync with EGL"))
# FIXME: Until it is ready # FIXME: Until it is ready
@ -37,7 +36,6 @@ class ImportSyncTabs(SideTabWidget):
class ImportSyncWidget(QWidget): class ImportSyncWidget(QWidget):
def __init__(self, widget: QWidget, title: str, info: str, parent=None): def __init__(self, widget: QWidget, title: str, info: str, parent=None):
super(ImportSyncWidget, self).__init__(parent=parent) super(ImportSyncWidget, self).__init__(parent=parent)
self.layout = QVBoxLayout() self.layout = QVBoxLayout()
@ -48,5 +46,7 @@ class ImportSyncWidget(QWidget):
self.layout.addWidget(self.group) self.layout.addWidget(self.group)
self.info = QLabel(f"<h4>{info}</h4>") self.info = QLabel(f"<h4>{info}</h4>")
self.layout.addWidget(self.info) 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) self.setLayout(self.layout)

View file

@ -8,7 +8,9 @@ from PyQt5.QtWidgets import QGroupBox, QListWidgetItem, QFileDialog, QMessageBox
import rare.shared as shared 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_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.extra_widgets import PathEdit
from rare.utils.models import PathSpec from rare.utils.models import PathSpec
from rare.utils.utils import WineResolver from rare.utils.utils import WineResolver
@ -17,33 +19,36 @@ logger = getLogger("EGLSync")
class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup): class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
def __init__(self, parent=None): def __init__(self, parent=None):
super(EGLSyncGroup, self).__init__(parent=parent) super(EGLSyncGroup, self).__init__(parent=parent)
self.setupUi(self) self.setupUi(self)
self.egl_path_info.setProperty('infoLabel', 1) self.egl_path_info.setProperty("infoLabel", 1)
self.thread_pool = QThreadPool.globalInstance() self.thread_pool = QThreadPool.globalInstance()
if platform.system() == 'Windows': if platform.system() == "Windows":
self.egl_path_edit_label.setVisible(False) self.egl_path_edit_label.setVisible(False)
self.egl_path_info_label.setVisible(False) self.egl_path_info_label.setVisible(False)
self.egl_path_info.setVisible(False) self.egl_path_info.setVisible(False)
else: else:
self.egl_path_edit = PathEdit( self.egl_path_edit = PathEdit(
path=shared.core.egl.programdata_path, 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, file_type=QFileDialog.DirectoryOnly,
edit_func=self.egl_path_edit_edit_cb, edit_func=self.egl_path_edit_edit_cb,
save_func=self.egl_path_edit_save_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.textChanged.connect(self.egl_path_changed)
self.egl_path_edit_layout.addWidget(self.egl_path_edit) self.egl_path_edit_layout.addWidget(self.egl_path_edit)
if not shared.core.egl.programdata_path: if not shared.core.egl.programdata_path:
self.egl_path_info.setText(self.tr('Updating...')) self.egl_path_info.setText(self.tr("Updating..."))
wine_resolver = WineResolver(PathSpec.egl_programdata, 'default', shared.core) wine_resolver = WineResolver(
PathSpec.egl_programdata, "default", shared.core
)
wine_resolver.signals.result_ready.connect(self.wine_resolver_cb) wine_resolver.signals.result_ready.connect(self.wine_resolver_cb)
self.thread_pool.start(wine_resolver) self.thread_pool.start(wine_resolver)
else: else:
@ -67,12 +72,18 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
self.egl_path_info.setText(path) self.egl_path_info.setText(path)
if not path: if not path:
self.egl_path_info.setText( self.egl_path_info.setText(
self.tr('Default Wine prefix is unset, or path does not exist. ' self.tr(
'Create it or configure it in Settings -> Linux.')) "Default Wine prefix is unset, or path does not exist. "
"Create it or configure it in Settings -> Linux."
)
)
elif not os.path.exists(path): elif not os.path.exists(path):
self.egl_path_info.setText( self.egl_path_info.setText(
self.tr('Default Wine prefix is set but EGL manifests path does not exist. ' self.tr(
'Your configured default Wine prefix might not be where EGL is installed.')) "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: else:
self.egl_path_edit.setText(path) 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]: def egl_path_edit_edit_cb(path) -> Tuple[bool, str]:
if not path: if not path:
return True, 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 is a wine prefix
path = os.path.join(path, 'dosdevices/c:', 'ProgramData/Epic/EpicGamesLauncher/Data/Manifests') path = os.path.join(
elif not path.rstrip('/').endswith('ProgramData/Epic/EpicGamesLauncher/Data/Manifests'): 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 # lower() might or might not be needed in the check
return False, path return False, path
if os.path.exists(path): if os.path.exists(path):
@ -95,11 +114,11 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
if not path or not os.path.exists(path): if not path or not os.path.exists(path):
# This is the same as "--unlink" # This is the same as "--unlink"
shared.core.egl.programdata_path = None shared.core.egl.programdata_path = None
shared.core.lgd.config.remove_option('Legendary', 'egl_programdata') 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_sync")
# remove EGL GUIDs from all games, DO NOT remove .egstore folders because that would fuck things up. # 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(): for igame in shared.core.get_installed_list():
igame.egl_guid = '' igame.egl_guid = ""
shared.core.install_game(igame) shared.core.install_game(igame)
else: else:
shared.core.egl.programdata_path = path shared.core.egl.programdata_path = path
@ -119,9 +138,9 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
if state == Qt.Unchecked: if state == Qt.Unchecked:
self.import_list.setEnabled(bool(self.import_list.items)) self.import_list.setEnabled(bool(self.import_list.items))
self.export_list.setEnabled(bool(self.export_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: 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 # lk: do import/export here since automatic sync was selected
self.import_list.mark(Qt.Checked) self.import_list.mark(Qt.Checked)
self.export_list.mark(Qt.Checked) self.export_list.mark(Qt.Checked)
@ -134,7 +153,9 @@ class EGLSyncGroup(QGroupBox, Ui_EGLSyncGroup):
def update_lists(self): def update_lists(self):
# self.egl_watcher.blockSignals(True) # 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 # NOTE: need to clear known manifests to force refresh
shared.core.egl.manifests.clear() shared.core.egl.manifests.clear()
self.egl_sync_check_label.setEnabled(have_path) self.egl_sync_check_label.setEnabled(have_path)
@ -178,7 +199,6 @@ class EGLSyncListItem(QListWidgetItem):
class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup): class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup):
def __init__(self, export: bool, parent=None): def __init__(self, export: bool, parent=None):
super(EGLSyncListGroup, self).__init__(parent=parent) super(EGLSyncListGroup, self).__init__(parent=parent)
self.setupUi(self) self.setupUi(self)
@ -187,19 +207,20 @@ class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup):
self.export = export self.export = export
if export: if export:
self.setTitle(self.tr('Exportable games')) self.setTitle(self.tr("Exportable games"))
self.label.setText(self.tr('No games to export to EGL')) self.label.setText(self.tr("No games to export to EGL"))
self.action_button.setText(self.tr('Export')) self.action_button.setText(self.tr("Export"))
self.list_func = shared.core.egl_get_exportable self.list_func = shared.core.egl_get_exportable
else: else:
self.setTitle(self.tr('Importable games')) self.setTitle(self.tr("Importable games"))
self.label.setText(self.tr('No games to import from EGL')) self.label.setText(self.tr("No games to import from EGL"))
self.action_button.setText(self.tr('Import')) self.action_button.setText(self.tr("Import"))
self.list_func = shared.core.egl_get_importable self.list_func = shared.core.egl_get_importable
self.list.itemDoubleClicked.connect( self.list.itemDoubleClicked.connect(
lambda item: lambda item: item.setCheckState(Qt.Unchecked)
item.setCheckState(Qt.Unchecked) if item.checkState() != Qt.Unchecked else item.setCheckState(Qt.Checked) if item.checkState() != Qt.Unchecked
else item.setCheckState(Qt.Checked)
) )
self.list.itemChanged.connect(self.has_selected) self.list.itemChanged.connect(self.has_selected)
@ -244,9 +265,10 @@ class EGLSyncListGroup(QGroupBox, Ui_EGLSyncListGroup):
if errors: if errors:
QMessageBox.warning( QMessageBox.warning(
self.parent(), self.parent(),
self.tr('The following errors occurred while {}.').format( self.tr("The following errors occurred while {}.").format(
self.tr('exporting') if self.export else self.tr('importing')), self.tr("exporting") if self.export else self.tr("importing")
'\n'.join(errors) ),
"\n".join(errors),
) )
@property @property
@ -269,7 +291,7 @@ class EGLSyncWorker(QRunnable):
self.export_list.action() self.export_list.action()
''' """
from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QCheckBox, QPushButton, QDialog 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: on update_egl_widget this is going to crash because
# FIXME: the item is not removed from the list in the python's side # FIXME: the item is not removed from the list in the python's side
self.deleteLater() self.deleteLater()
''' """

View file

@ -21,7 +21,9 @@ class AppNameCompleter(QCompleter):
def __init__(self, app_names: List, parent=None): def __init__(self, app_names: List, parent=None):
super(AppNameCompleter, self).__init__(parent) super(AppNameCompleter, self).__init__(parent)
# pylint: disable=E1136 # 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) model = QStandardItemModel(len(app_names), 2)
for idx, game in enumerate(app_names): 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 # lk: Getting the index from the popup and trying to use it in the completer will return invalid results
if isinstance(idx, QModelIndex): if isinstance(idx, QModelIndex):
self.activated.emit( self.activated.emit(
self.popup().model().data( self.popup().model().data(self.popup().model().index(idx.row(), 1))
self.popup().model().index(idx.row(), 1)
)
) )
# TODO: implement conversion from app_name to app_title (signal loop here) # TODO: implement conversion from app_name to app_title (signal loop here)
# if isinstance(idx_str, str): # if isinstance(idx_str, str):
@ -67,23 +67,31 @@ class ImportGroup(QGroupBox, Ui_ImportGroup):
self.core = shared.core self.core = shared.core
self.app_name_list = [game.app_name for game in shared.api_results.game_list] self.app_name_list = [game.app_name for game in shared.api_results.game_list]
self.install_dir_list = [ self.install_dir_list = [
game.metadata.get('customAttributes', {}).get('FolderName', {}).get('value', game.app_name) game.metadata.get("customAttributes", {})
for game in shared.api_results.game_list if not game.is_dlc] .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.path_edit = PathEdit(
self.core.get_default_install_dir(), self.core.get_default_install_dir(),
QFileDialog.DirectoryOnly, QFileDialog.DirectoryOnly,
edit_func=self.path_edit_cb, edit_func=self.path_edit_cb,
parent=self parent=self,
) )
self.path_edit.textChanged.connect(self.path_changed) self.path_edit.textChanged.connect(self.path_changed)
self.path_edit_layout.addWidget(self.path_edit) self.path_edit_layout.addWidget(self.path_edit)
self.app_name = IndicatorLineEdit( self.app_name = IndicatorLineEdit(
ph_text=self.tr("Use in case the app name was not found automatically"), 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, edit_func=self.app_name_edit_cb,
parent=self parent=self,
) )
self.app_name.textChanged.connect(self.app_name_changed) self.app_name.textChanged.connect(self.app_name_changed)
self.app_name_layout.addWidget(self.app_name) 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")) self.info_label.setText(self.tr("Could not find app name"))
return 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) 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()) self.app_name.setText(str())
shared.signals.update_gamelist.emit([app_name]) 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 # update available
shared.signals.add_download.emit(igame.app_name) shared.signals.add_download.emit(igame.app_name)
shared.signals.update_download_tab_text.emit() shared.signals.update_download_tab_text.emit()
else: else:
logger.warning(f'Failed to import "{app_name}"') 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 return

View file

@ -22,6 +22,8 @@ class SettingsTab(SideTabWidget):
self.about = About() self.about = About()
self.addTab(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) self.setCurrentIndex(0)

View file

@ -33,10 +33,14 @@ class About(QWidget, Ui_About):
self.open_browser.setVisible(False) self.open_browser.setVisible(False)
self.manager = QtRequestManager("json") 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( 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): def update_available_finished(self, data: dict):
if latest_tag := data.get("tag_name"): if latest_tag := data.get("tag_name"):
@ -46,7 +50,9 @@ class About(QWidget, Ui_About):
if self.update_available: if self.update_available:
logger.info(f"Update available: {__version__} -> {latest_tag}") 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_label.setVisible(True)
self.update_lbl.setVisible(True) self.update_lbl.setVisible(True)
self.open_browser.setVisible(True) self.open_browser.setVisible(True)

View file

@ -9,7 +9,6 @@ logger = getLogger("DXVK Settings")
class DxvkSettings(QGroupBox, Ui_DxvkSettings): class DxvkSettings(QGroupBox, Ui_DxvkSettings):
def __init__(self, name=None): def __init__(self, name=None):
super(DxvkSettings, self).__init__() super(DxvkSettings, self).__init__()
self.setupUi(self) 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 # Custom Options, index 3, adds DXVK_HUD=devinfo,fps and enables the customization panel
def load_settings(self): 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) self.gb_dxvk_options.setDisabled(True)
if dxvk_options is not None: if dxvk_options is not None:
if dxvk_options == "0": if dxvk_options == "0":
@ -74,16 +75,23 @@ class DxvkSettings(QGroupBox, Ui_DxvkSettings):
dxvk_options.append(opt) dxvk_options.append(opt)
if not dxvk_options: if not dxvk_options:
# Check if this is the first activation # 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"]: if stored not in [None, "0", "1"]:
self.core.lgd.config[f"{self.name}.env"]["DXVK_HUD"] = "0" self.core.lgd.config[f"{self.name}.env"]["DXVK_HUD"] = "0"
else: else:
dxvk_options = ["devinfo", "fps"] dxvk_options = ["devinfo", "fps"]
# Check again if dxvk_options changed due to first activation # Check again if dxvk_options changed due to first activation
if dxvk_options: 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: 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") self.core.lgd.config.remove_option(f"{self.name}.env", "DXVK_HUD")
if not self.core.lgd.config[f"{self.name}.env"]: if not self.core.lgd.config[f"{self.name}.env"]:
self.core.lgd.config.remove_section(f"{self.name}.env") self.core.lgd.config.remove_section(f"{self.name}.env")

View file

@ -21,42 +21,46 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
self.core = shared.core self.core = shared.core
# Default installation directory # Default installation directory
self.install_dir = PathEdit(self.core.get_default_install_dir(), self.install_dir = PathEdit(
file_type=QFileDialog.DirectoryOnly, self.core.get_default_install_dir(),
save_func=self.path_save) file_type=QFileDialog.DirectoryOnly,
save_func=self.path_save,
)
self.install_dir_layout.addWidget(self.install_dir) self.install_dir_layout.addWidget(self.install_dir)
# Max Workers # 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.setValue(max_workers)
self.max_worker_spin.valueChanged.connect(self.max_worker_save) self.max_worker_spin.valueChanged.connect(self.max_worker_save)
# Max memory # 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.setValue(max_memory)
self.max_memory_spin.valueChanged.connect(self.max_memory_save) self.max_memory_spin.valueChanged.connect(self.max_memory_save)
# Preferred CDN # 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.setText(preferred_cdn)
self.preferred_cdn_line.textChanged.connect(self.preferred_cdn_save) self.preferred_cdn_line.textChanged.connect(self.preferred_cdn_save)
# Disable HTTPS # 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.setChecked(disable_https)
self.disable_https_check.stateChanged.connect(self.disable_https_save) self.disable_https_check.stateChanged.connect(self.disable_https_save)
# Cleanup # Cleanup
self.clean_button.clicked.connect( self.clean_button.clicked.connect(lambda: self.cleanup(False))
lambda: self.cleanup(False) self.clean_keep_manifests_button.clicked.connect(lambda: self.cleanup(True))
)
self.clean_keep_manifests_button.clicked.connect(
lambda: self.cleanup(True)
)
self.locale_edit = IndicatorLineEdit( self.locale_edit = IndicatorLineEdit(
f"{self.core.language_code}-{self.core.country_code}", f"{self.core.language_code}-{self.core.country_code}",
edit_func=self.locale_edit_cb, edit_func=self.locale_edit_cb,
save_func=self.locale_save_cb, save_func=self.locale_save_cb,
horiz_policy=QSizePolicy.Minimum, horiz_policy=QSizePolicy.Minimum,
parent=self parent=self,
) )
self.locale_layout.addWidget(self.locale_edit) self.locale_layout.addWidget(self.locale_edit)
@ -111,28 +115,41 @@ class LegendarySettings(QWidget, Ui_LegendarySettings):
self.core.lgd.save_config() self.core.lgd.save_config()
def disable_https_save(self, checked: int): 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() self.core.lgd.save_config()
def cleanup(self, keep_manifests: bool): def cleanup(self, keep_manifests: bool):
before = self.core.lgd.get_dir_size() 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)) app_names = set(g.app_name for g in self.core.get_assets(update_assets=False))
self.core.lgd.clean_metadata(app_names) self.core.lgd.clean_metadata(app_names)
if not keep_manifests: if not keep_manifests:
logger.debug('Removing manifests...') logger.debug("Removing manifests...")
installed = [(ig.app_name, ig.version) for ig in self.core.get_installed_list()] installed = [
installed.extend((ig.app_name, ig.version) for ig in self.core.get_installed_dlc_list()) (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) self.core.lgd.clean_manifests(installed)
logger.debug('Removing tmp data') logger.debug("Removing tmp data")
self.core.lgd.clean_tmp_data() self.core.lgd.clean_tmp_data()
after = self.core.lgd.get_dir_size() 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: if (before - after) > 0:
QMessageBox.information(self, "Cleanup", self.tr("Cleanup complete! Successfully removed {}").format( QMessageBox.information(
get_size(before - after))) self,
"Cleanup",
self.tr("Cleanup complete! Successfully removed {}").format(
get_size(before - after)
),
)
else: else:
QMessageBox.information(self, "Cleanup", "Nothing to clean") QMessageBox.information(self, "Cleanup", "Nothing to clean")

View file

@ -21,7 +21,8 @@ class LinuxSettings(QWidget, Ui_LinuxSettings):
self.wine_prefix = PathEdit( self.wine_prefix = PathEdit(
self.load_prefix(), self.load_prefix(),
file_type=QFileDialog.DirectoryOnly, file_type=QFileDialog.DirectoryOnly,
save_func=self.save_prefix) save_func=self.save_prefix,
)
self.prefix_layout.addWidget(self.wine_prefix) self.prefix_layout.addWidget(self.wine_prefix)
# Wine executable # Wine executable
@ -29,7 +30,10 @@ class LinuxSettings(QWidget, Ui_LinuxSettings):
self.load_setting(self.name, "wine_executable"), self.load_setting(self.name, "wine_executable"),
file_type=QFileDialog.ExistingFile, file_type=QFileDialog.ExistingFile,
name_filter="Wine executable (wine wine64)", 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) self.exec_layout.addWidget(self.wine_exec)
# dxvk # dxvk
@ -37,13 +41,15 @@ class LinuxSettings(QWidget, Ui_LinuxSettings):
self.dxvk_layout.addWidget(self.dxvk) self.dxvk_layout.addWidget(self.dxvk)
def load_prefix(self) -> str: def load_prefix(self) -> str:
return self.load_setting(f'{self.name}.env', return self.load_setting(
'WINEPREFIX', f"{self.name}.env",
fallback=self.load_setting(self.name, 'wine_prefix')) "WINEPREFIX",
fallback=self.load_setting(self.name, "wine_prefix"),
)
def save_prefix(self, text: str): def save_prefix(self, text: str):
self.save_setting(text, f'{self.name}.env', 'WINEPREFIX') self.save_setting(text, f"{self.name}.env", "WINEPREFIX")
self.save_setting(text, self.name, 'wine_prefix') self.save_setting(text, self.name, "wine_prefix")
@staticmethod @staticmethod
def load_setting(section: str, setting: str, fallback: str = str()): 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}") logger.debug(f"Set {setting} in {f'[{section}]'} to {text}")
else: 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) shared.core.lgd.config.remove_option(section, setting)
logger.debug(f"Unset {setting} from {f'[{section}]'}") logger.debug(f"Unset {setting} from {f'[{section}]'}")
if not shared.core.lgd.config[section]: if not shared.core.lgd.config[section]:

View file

@ -11,15 +11,17 @@ from rare import cache_dir, shared
from rare.components.tabs.settings.rpc import RPCSettings from rare.components.tabs.settings.rpc import RPCSettings
from rare.ui.components.tabs.settings.rare import Ui_RareSettings from rare.ui.components.tabs.settings.rare import Ui_RareSettings
from rare.utils import utils 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") logger = getLogger("RareSettings")
languages = [ languages = [("en", "English"), ("de", "Deutsch"), ("fr", "Français")]
("en", "English"),
("de", "Deutsch"),
("fr", "Français")
]
class RareSettings(QWidget, Ui_RareSettings): class RareSettings(QWidget, Ui_RareSettings):
@ -84,27 +86,35 @@ class RareSettings(QWidget, Ui_RareSettings):
lambda: self.settings.setValue("auto_update", self.auto_update.isChecked()) lambda: self.settings.setValue("auto_update", self.auto_update.isChecked())
) )
self.confirm_start.stateChanged.connect( 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( 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( self.notification.stateChanged.connect(
lambda: self.settings.setValue("notification", self.notification.isChecked()) lambda: self.settings.setValue(
) "notification", self.notification.isChecked()
self.save_size.stateChanged.connect( )
self.save_window_size
) )
self.save_size.stateChanged.connect(self.save_window_size)
self.log_games.stateChanged.connect( self.log_games.stateChanged.connect(
lambda: self.settings.setValue("show_console", self.log_games.isChecked()) lambda: self.settings.setValue("show_console", self.log_games.isChecked())
) )
if platform.system() == "Linux": if platform.system() == "Linux":
self.desktop_file = os.path.expanduser("~/Desktop/Rare.desktop") 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": elif platform.system() == "Windows":
self.desktop_file = os.path.expanduser("~/Desktop/Rare.lnk") 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: else:
self.desktop_link_btn.setText(self.tr("Not supported")) self.desktop_link_btn.setText(self.tr("Not supported"))
self.desktop_link_btn.setDisabled(True) 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")) self.startmenu_link_btn.setText(self.tr("Create start menu link"))
except PermissionError as e: except PermissionError as e:
logger.error(str(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): def create_desktop_link(self):
try: try:
@ -162,7 +176,11 @@ class RareSettings(QWidget, Ui_RareSettings):
os.remove(self.desktop_file) os.remove(self.desktop_file)
self.desktop_link_btn.setText(self.tr("Create desktop link")) self.desktop_link_btn.setText(self.tr("Create desktop link"))
except PermissionError as e: 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): def on_color_select_changed(self, color):
if color: if color:

View file

@ -27,20 +27,20 @@ class UbiGetInfoWorker(QRunnable):
try: try:
external_auths = shared.core.egs.get_external_auths() external_auths = shared.core.egs.get_external_auths()
for ext_auth in external_auths: for ext_auth in external_auths:
if ext_auth['type'] != 'ubisoft': if ext_auth["type"] != "ubisoft":
continue continue
ubi_account_id = ext_auth['externalAuthId'] ubi_account_id = ext_auth["externalAuthId"]
break break
else: else:
self.signals.worker_finished.emit(set(), set(), "") self.signals.worker_finished.emit(set(), set(), "")
return return
uplay_keys = shared.core.egs.store_get_uplay_codes() uplay_keys = shared.core.egs.store_get_uplay_codes()
key_list = uplay_keys['data']['PartnerIntegration']['accountUplayCodes'] key_list = uplay_keys["data"]["PartnerIntegration"]["accountUplayCodes"]
redeemed = {k['gameId'] for k in key_list if k['redeemedOnUplay']} redeemed = {k["gameId"] for k in key_list if k["redeemedOnUplay"]}
entitlements = shared.core.egs.get_user_entitlements() 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) self.signals.worker_finished.emit(redeemed, entitlements, ubi_account_id)
except Exception as e: except Exception as e:
logger.error(str(e)) logger.error(str(e))
@ -61,7 +61,9 @@ class UbiConnectWorker(QRunnable):
self.signals.linked.emit("") self.signals.linked.emit("")
return return
try: 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) shared.core.egs.store_redeem_uplay_codes(self.ubi_account_id)
except Exception as e: except Exception as e:
self.signals.linked.emit(str(e)) self.signals.linked.emit(str(e))
@ -85,14 +87,18 @@ class UbiLinkWidget(QWidget):
self.ok_indicator.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred) self.ok_indicator.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Preferred)
self.layout().addWidget(self.ok_indicator) 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.layout().addWidget(self.link_button)
self.link_button.clicked.connect(self.activate) self.link_button.clicked.connect(self.activate)
def activate(self): def activate(self):
self.link_button.setDisabled(True) self.link_button.setDisabled(True)
# self.ok_indicator.setPixmap(icon("mdi.loading", color="grey").pixmap(20, 20)) # 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: if shared.args.debug:
worker = UbiConnectWorker(None, None) worker = UbiConnectWorker(None, None)
@ -103,11 +109,15 @@ class UbiLinkWidget(QWidget):
def worker_finished(self, error): def worker_finished(self, error):
if not 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.setDisabled(True)
self.link_button.setText(self.tr("Already activated")) self.link_button.setText(self.tr("Already activated"))
else: 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.ok_indicator.setToolTip(error)
self.link_button.setText(self.tr("Try again")) self.link_button.setText(self.tr("Try again"))
self.link_button.setDisabled(False) 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): def show_ubi_games(self, redeemed: set, entitlements: set, ubi_account_id: str):
if not redeemed and ubi_account_id != "error": 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( 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 = 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) self.widget.layout().addWidget(open_browser_button)
return return
elif ubi_account_id == "error": elif ubi_account_id == "error":
@ -141,17 +160,19 @@ class UbiActivationHelper(QObject):
uplay_games = [] uplay_games = []
activated = 0 activated = 0
for game in games: for game in games:
for dlc_data in game.metadata.get('dlcItemList', []): for dlc_data in game.metadata.get("dlcItemList", []):
if dlc_data['entitlementName'] not in entitlements: if dlc_data["entitlementName"] not in entitlements:
continue continue
try: try:
app_name = dlc_data['releaseInfo'][0]['appId'] app_name = dlc_data["releaseInfo"][0]["appId"]
except (IndexError, KeyError): except (IndexError, KeyError):
app_name = 'unknown' app_name = "unknown"
dlc_game = Game(app_name=app_name, app_title=dlc_data['title'], metadata=dlc_data) dlc_game = Game(
if dlc_game.partner_link_type != 'ubisoft': app_name=app_name, app_title=dlc_data["title"], metadata=dlc_data
)
if dlc_game.partner_link_type != "ubisoft":
continue continue
if dlc_game.partner_link_id in redeemed: if dlc_game.partner_link_id in redeemed:
continue continue
@ -167,14 +188,22 @@ class UbiActivationHelper(QObject):
if not uplay_games: if not uplay_games:
if activated >= 1: if activated >= 1:
self.widget.layout().addWidget( 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: 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: 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) self.widget.layout().addWidget(widget)
return 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: for game in uplay_games:
widget = UbiLinkWidget(game, ubi_account_id) widget = UbiLinkWidget(game, ubi_account_id)

View file

@ -15,8 +15,11 @@ class Shop(QStackedWidget):
def __init__(self, core: LegendaryCore): def __init__(self, core: LegendaryCore):
super(Shop, self).__init__() super(Shop, self).__init__()
self.core = core self.core = core
self.api_core = ShopApiCore(self.core.egs.session.headers["Authorization"], self.core.language_code, self.api_core = ShopApiCore(
self.core.country_code) self.core.egs.session.headers["Authorization"],
self.core.language_code,
self.core.country_code,
)
self.shop = ShopWidget(cache_dir, core, self.api_core) self.shop = ShopWidget(cache_dir, core, self.api_core)
self.wishlist_widget = Wishlist(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.search_results = SearchResults(self.api_core)
self.addWidget(self.search_results) self.addWidget(self.search_results)
self.search_results.show_info.connect(self.show_game_info) 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.addWidget(self.info)
self.info.back_button.clicked.connect(lambda: self.setCurrentIndex(0)) self.info.back_button.clicked.connect(lambda: self.setCurrentIndex(0))

View file

@ -5,21 +5,24 @@ from PyQt5.QtCore import QObject
class Constants(QObject): class Constants(QObject):
def __init__(self): def __init__(self):
super(Constants, self).__init__() super(Constants, self).__init__()
self.categories = sorted([ self.categories = sorted(
(self.tr("Action"), "1216"), [
(self.tr("Adventure"), "1117"), (self.tr("Action"), "1216"),
(self.tr("Puzzle"), "1298"), (self.tr("Adventure"), "1117"),
(self.tr("Open world"), "1307"), (self.tr("Puzzle"), "1298"),
(self.tr("Racing"), "1212"), (self.tr("Open world"), "1307"),
(self.tr("RPG"), "1367"), (self.tr("Racing"), "1212"),
(self.tr("Shooter"), "1210"), (self.tr("RPG"), "1367"),
(self.tr("Strategy"), "1115"), (self.tr("Shooter"), "1210"),
(self.tr("Survival"), "1080"), (self.tr("Strategy"), "1115"),
(self.tr("First Person"), "1294"), (self.tr("Survival"), "1080"),
(self.tr("Indie"), "1263"), (self.tr("First Person"), "1294"),
(self.tr("Simulation"), "1393"), (self.tr("Indie"), "1263"),
(self.tr("Sport"), "1283") (self.tr("Simulation"), "1393"),
], key=lambda x: x[0]) (self.tr("Sport"), "1283"),
],
key=lambda x: x[0],
)
self.platforms = [ self.platforms = [
("MacOS", "9548"), ("MacOS", "9548"),
@ -37,74 +40,78 @@ class Constants(QObject):
(self.tr("Game"), "games/edition/base"), (self.tr("Game"), "games/edition/base"),
(self.tr("Bundle"), "bundles/games"), (self.tr("Bundle"), "bundles/games"),
(self.tr("Add-on"), "addons"), (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!, " \ game_query = (
"$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " \ "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, "
"$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean " \ "$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, "
"= false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, " \ "$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean "
"$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n " \ "= false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, "
"category: $category\n count: $count\n country: $country\n keywords: $keywords\n " \ "$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n "
"locale: $locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n " \ "category: $category\n count: $count\n country: $country\n keywords: $keywords\n "
"sortDir: $sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n " \ "locale: $locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n "
"priceRange: $priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: " \ "sortDir: $sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n "
"$effectiveDate\n ) {\n elements {\n title\n id\n namespace\n " \ "priceRange: $priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: "
"description\n effectiveDate\n keyImages {\n type\n url\n }\n " \ "$effectiveDate\n ) {\n elements {\n title\n id\n namespace\n "
" currentPrice\n seller {\n id\n name\n }\n productSlug\n " \ "description\n effectiveDate\n keyImages {\n type\n url\n }\n "
" urlSlug\n url\n tags {\n id\n }\n items {\n id\n " \ " currentPrice\n seller {\n id\n name\n }\n productSlug\n "
" namespace\n }\n customAttributes {\n key\n value\n }\n " \ " urlSlug\n url\n tags {\n id\n }\n items {\n id\n "
"categories {\n path\n }\n catalogNs @include(if: $withMapping) {\n " \ " namespace\n }\n customAttributes {\n key\n value\n }\n "
"mappings(pageType: \"productHome\") {\n pageSlug\n pageType\n }\n " \ "categories {\n path\n }\n catalogNs @include(if: $withMapping) {\n "
"}\n offerMappings @include(if: $withMapping) {\n pageSlug\n pageType\n " \ 'mappings(pageType: "productHome") {\n pageSlug\n pageType\n }\n '
"}\n price(country: $country) @include(if: $withPrice) {\n totalPrice {\n " \ "}\n offerMappings @include(if: $withMapping) {\n pageSlug\n pageType\n "
"discountPrice\n originalPrice\n voucherDiscount\n discount\n " \ "}\n price(country: $country) @include(if: $withPrice) {\n totalPrice {\n "
" currencyCode\n currencyInfo {\n decimals\n }\n fmtPrice(" \ "discountPrice\n originalPrice\n voucherDiscount\n discount\n "
"locale: $locale) {\n originalPrice\n discountPrice\n " \ " currencyCode\n currencyInfo {\n decimals\n }\n fmtPrice("
"intermediatePrice\n }\n }\n lineOffers {\n appliedRules {\n " \ "locale: $locale) {\n originalPrice\n discountPrice\n "
" id\n endDate\n discountSetting {\n discountType\n " \ "intermediatePrice\n }\n }\n lineOffers {\n appliedRules {\n "
" }\n }\n }\n }\n promotions(category: $category) @include(if: " \ " id\n endDate\n discountSetting {\n discountType\n "
"$withPromotions) {\n promotionalOffers {\n promotionalOffers {\n " \ " }\n }\n }\n }\n promotions(category: $category) @include(if: "
"startDate\n endDate\n discountSetting {\n discountType\n " \ "$withPromotions) {\n promotionalOffers {\n promotionalOffers {\n "
" discountPercentage\n }\n }\n }\n " \ "startDate\n endDate\n discountSetting {\n discountType\n "
"upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n " \ " discountPercentage\n }\n }\n }\n "
"endDate\n discountSetting {\n discountType\n " \ "upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n "
"discountPercentage\n }\n }\n }\n }\n }\n paging {\n " \ "endDate\n discountSetting {\n discountType\n "
" count\n total\n }\n }\n }\n}\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!, " \ search_query = (
"$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, " \ "query searchStoreQuery($allowCountries: String, $category: String, $count: Int, $country: String!, "
"$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean = " \ "$keywords: String, $locale: String, $namespace: String, $withMapping: Boolean = false, $itemNs: String, "
"false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, " \ "$sortBy: String, $sortDir: String, $start: Int, $tag: String, $releaseDate: String, $withPrice: Boolean = "
"$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n " \ "false, $withPromotions: Boolean = false, $priceRange: String, $freeGame: Boolean, $onSale: Boolean, "
"category: $category\n count: $count\n country: $country\n keywords: $keywords\n locale: " \ "$effectiveDate: String) {\n Catalog {\n searchStore(\n allowCountries: $allowCountries\n "
"$locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n sortDir: " \ "category: $category\n count: $count\n country: $country\n keywords: $keywords\n locale: "
"$sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n priceRange: " \ "$locale\n namespace: $namespace\n itemNs: $itemNs\n sortBy: $sortBy\n sortDir: "
"$priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: $effectiveDate\n ) {" \ "$sortDir\n releaseDate: $releaseDate\n start: $start\n tag: $tag\n priceRange: "
"\n elements {\n title\n id\n namespace\n description\n " \ "$priceRange\n freeGame: $freeGame\n onSale: $onSale\n effectiveDate: $effectiveDate\n ) {"
"effectiveDate\n keyImages {\n type\n url\n }\n currentPrice\n " \ "\n elements {\n title\n id\n namespace\n description\n "
"seller {\n id\n name\n }\n productSlug\n urlSlug\n url\n " \ "effectiveDate\n keyImages {\n type\n url\n }\n currentPrice\n "
" tags {\n id\n }\n items {\n id\n namespace\n }\n " \ "seller {\n id\n name\n }\n productSlug\n urlSlug\n url\n "
"customAttributes {\n key\n value\n }\n categories {\n path\n " \ " tags {\n id\n }\n items {\n id\n namespace\n }\n "
"}\n catalogNs @include(if: $withMapping) {\n mappings(pageType: \"productHome\") {\n " \ "customAttributes {\n key\n value\n }\n categories {\n path\n "
" pageSlug\n pageType\n }\n }\n offerMappings @include(if: $withMapping) " \ '}\n catalogNs @include(if: $withMapping) {\n mappings(pageType: "productHome") {\n '
"{\n pageSlug\n pageType\n }\n price(country: $country) @include(if: " \ " pageSlug\n pageType\n }\n }\n offerMappings @include(if: $withMapping) "
"$withPrice) {\n totalPrice {\n discountPrice\n originalPrice\n " \ "{\n pageSlug\n pageType\n }\n price(country: $country) @include(if: "
"voucherDiscount\n discount\n currencyCode\n currencyInfo {\n " \ "$withPrice) {\n totalPrice {\n discountPrice\n originalPrice\n "
"decimals\n }\n fmtPrice(locale: $locale) {\n originalPrice\n " \ "voucherDiscount\n discount\n currencyCode\n currencyInfo {\n "
"discountPrice\n intermediatePrice\n }\n }\n lineOffers {\n " \ "decimals\n }\n fmtPrice(locale: $locale) {\n originalPrice\n "
" appliedRules {\n id\n endDate\n discountSetting {\n " \ "discountPrice\n intermediatePrice\n }\n }\n lineOffers {\n "
"discountType\n }\n }\n }\n }\n promotions(category: " \ " appliedRules {\n id\n endDate\n discountSetting {\n "
"$category) @include(if: $withPromotions) {\n promotionalOffers {\n promotionalOffers {\n " \ "discountType\n }\n }\n }\n }\n promotions(category: "
" startDate\n endDate\n discountSetting {\n " \ "$category) @include(if: $withPromotions) {\n promotionalOffers {\n promotionalOffers {\n "
"discountType\n discountPercentage\n }\n }\n }\n " \ " startDate\n endDate\n discountSetting {\n "
"upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n " \ "discountType\n discountPercentage\n }\n }\n }\n "
"endDate\n discountSetting {\n discountType\n discountPercentage\n " \ "upcomingPromotionalOffers {\n promotionalOffers {\n startDate\n "
" }\n }\n }\n }\n }\n paging {\n count\n " \ "endDate\n discountSetting {\n discountType\n discountPercentage\n "
"total\n }\n }\n }\n}\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" 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" 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" 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"

View file

@ -3,7 +3,16 @@ import webbrowser
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap, QFont 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 qtawesome import icon
from rare import shared from rare import shared
@ -29,7 +38,9 @@ class ShopGameInfo(QWidget, Ui_shop_info):
self.image_stack.addWidget(self.image) self.image_stack.addWidget(self.image)
self.image_stack.addWidget(WaitingSpinner()) self.image_stack.addWidget(WaitingSpinner())
warn_label = QLabel() 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.image_stack.addWidget(warn_label)
self.wishlist_button.clicked.connect(self.add_to_wishlist) 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")) # lambda success: self.wishlist_button.setText(self.tr("Remove from wishlist"))
# if success else self.wishlist_button.setText("Something goes wrong")) # if success else self.wishlist_button.setText("Something goes wrong"))
else: else:
self.api_core.remove_from_wishlist(self.game.namespace, self.game.offer_id, self.api_core.remove_from_wishlist(
lambda success: self.wishlist_button.setVisible(False) self.game.namespace,
if success else self.wishlist_button.setText("Something goes wrong")) 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): def data_received(self, game):
try: try:
@ -113,7 +128,12 @@ class ShopGameInfo(QWidget, Ui_shop_info):
self.price.setText("Error") self.price.setText("Error")
self.req_group_box.setVisible(False) self.req_group_box.setVisible(False)
for img in self.data.get("keyImages"): 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.update_image(img["url"], size=(240, 320))
self.image_stack.setCurrentIndex(0) self.image_stack.setCurrentIndex(0)
break break
@ -137,7 +157,10 @@ class ShopGameInfo(QWidget, Ui_shop_info):
font.setStrikeOut(True) font.setStrikeOut(True)
self.price.setFont(font) self.price.setFont(font)
self.discount_price.setText( 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) self.discount_price.setVisible(True)
else: else:
self.discount_price.setVisible(False) self.discount_price.setVisible(False)
@ -156,7 +179,9 @@ class ShopGameInfo(QWidget, Ui_shop_info):
req_widget.setLayout(QGridLayout()) req_widget.setLayout(QGridLayout())
req_widget.layout().addWidget(min_label, 0, 1) req_widget.layout().addWidget(min_label, 0, 1)
req_widget.layout().addWidget(rec_label, 0, 2) 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) req_widget.layout().addWidget(QLabel(key), i + 1, 0)
min_label = QLabel(value[0]) min_label = QLabel(value[0])
min_label.setWordWrap(True) min_label.setWordWrap(True)
@ -167,7 +192,9 @@ class ShopGameInfo(QWidget, Ui_shop_info):
req_tabs.addTab(req_widget, system) req_tabs.addTab(req_widget, system)
self.req_group_box.layout().addWidget(req_tabs) self.req_group_box.layout().addWidget(req_tabs)
else: 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) self.req_group_box.setVisible(True)
if self.game.image_urls.front_tall: if self.game.image_urls.front_tall:
img_url = 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)) self.tags.setText(", ".join(self.game.tags))
# clear Layout # 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): if not isinstance(widget, QSpacerItem):
widget.widget().deleteLater() widget.widget().deleteLater()
self.social_link_gb.deleteLater() self.social_link_gb.deleteLater()
@ -229,7 +259,10 @@ class ShopGameInfo(QWidget, Ui_shop_info):
self.wishlist.append(game["offer"]["title"]) self.wishlist.append(game["offer"]["title"])
def button_clicked(self): 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): class SocialButton(QPushButton):

View file

@ -42,14 +42,16 @@ class GameWidget(QWidget):
mini_layout.addWidget(self.title_label) mini_layout.addWidget(self.title_label)
mini_layout.addStretch(1) mini_layout.addStretch(1)
price = json_info['price']['totalPrice']['fmtPrice']['originalPrice'] price = json_info["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
discount_price = json_info['price']['totalPrice']['fmtPrice']['discountPrice'] discount_price = json_info["price"]["totalPrice"]["fmtPrice"]["discountPrice"]
price_label = QLabel(price) price_label = QLabel(price)
if price != discount_price: if price != discount_price:
font = QFont() font = QFont()
font.setStrikeOut(True) font.setStrikeOut(True)
price_label.setFont(font) 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) mini_layout.addWidget(price_label)
else: else:
if price == "0": if price == "0":
@ -64,10 +66,19 @@ class GameWidget(QWidget):
self.title = json_info["title"] self.title = json_info["title"]
for img in json_info["keyImages"]: 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": if img["type"] == "VaultClosed" and self.title != "Mystery Game":
continue 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 break
else: else:
logger.info(", ".join([img["type"] for img in json_info["keyImages"]])) logger.info(", ".join([img["type"] for img in json_info["keyImages"]]))
@ -95,8 +106,8 @@ class WishlistWidget(QWidget, Ui_WishlistWidget):
break break
else: else:
self.developer.setText(game["seller"]["name"]) self.developer.setText(game["seller"]["name"])
original_price = game['price']['totalPrice']['fmtPrice']['originalPrice'] original_price = game["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
discount_price = game['price']['totalPrice']['fmtPrice']['discountPrice'] discount_price = game["price"]["totalPrice"]["fmtPrice"]["discountPrice"]
self.price.setText(original_price if original_price != "0" else self.tr("Free")) self.price.setText(original_price if original_price != "0" else self.tr("Free"))
# if discount # if discount
@ -118,7 +129,9 @@ class WishlistWidget(QWidget, Ui_WishlistWidget):
url = image_model.offer_image_wide url = image_model.offer_image_wide
self.image.update_image(url, game.get("title"), (240, 135)) self.image.update_image(url, game.get("title"), (240, 135))
self.delete_button.setIcon(icon("mdi.delete", color="white")) 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: def mousePressEvent(self, e: QtGui.QMouseEvent) -> None:
# left button # left button

View file

@ -1,8 +1,16 @@
from PyQt5 import QtGui from PyQt5 import QtGui
from PyQt5.QtCore import pyqtSignal, Qt from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtGui import QFont from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QScrollArea, QGroupBox, QPushButton, \ from PyQt5.QtWidgets import (
QStackedWidget QWidget,
QVBoxLayout,
QHBoxLayout,
QLabel,
QScrollArea,
QGroupBox,
QPushButton,
QStackedWidget,
)
from rare.utils.extra_widgets import ImageLabel, FlowLayout, WaitingSpinner from rare.utils.extra_widgets import ImageLabel, FlowLayout, WaitingSpinner
@ -79,8 +87,8 @@ class _SearchResultItem(QGroupBox):
self.title.setFont(title_font) self.title.setFont(title_font)
self.title.setWordWrap(True) self.title.setWordWrap(True)
self.layout.addWidget(self.title) self.layout.addWidget(self.title)
price = result['price']['totalPrice']['fmtPrice']['originalPrice'] price = result["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
discount_price = result['price']['totalPrice']['fmtPrice']['discountPrice'] discount_price = result["price"]["totalPrice"]["fmtPrice"]["discountPrice"]
price_layout = QHBoxLayout() price_layout = QHBoxLayout()
price_label = QLabel(price if price != "0" else self.tr("Free")) price_label = QLabel(price if price != "0" else self.tr("Free"))
price_layout.addWidget(price_label) price_layout.addWidget(price_label)

View file

@ -3,8 +3,12 @@ from logging import getLogger
from PyQt5.QtCore import pyqtSignal, QObject from PyQt5.QtCore import pyqtSignal, QObject
from rare.components.tabs.shop.constants import wishlist_query, search_query, add_to_wishlist_query, \ from rare.components.tabs.shop.constants import (
remove_from_wishlist_query wishlist_query,
search_query,
add_to_wishlist_query,
remove_from_wishlist_query,
)
from rare.components.tabs.shop.shop_models import BrowseModel from rare.components.tabs.shop.shop_models import BrowseModel
from rare.utils.qt_requests import QtRequestManager from rare.utils.qt_requests import QtRequestManager
@ -46,13 +50,17 @@ class ShopApiCore(QObject):
handle_func(results) handle_func(results)
def get_wishlist(self, handle_func): def get_wishlist(self, handle_func):
self.auth_manager.post(graphql_url, { self.auth_manager.post(
"query": wishlist_query, graphql_url,
"variables": { {
"country": self.country_code, "query": wishlist_query,
"locale": self.language_code + "-" + self.country_code "variables": {
} "country": self.country_code,
}, lambda data: self._handle_wishlist(data, handle_func)) "locale": self.language_code + "-" + self.country_code,
},
},
lambda data: self._handle_wishlist(data, handle_func),
)
def _handle_wishlist(self, data, handle_func): def _handle_wishlist(self, data, handle_func):
try: try:
@ -71,13 +79,24 @@ class ShopApiCore(QObject):
def search_game(self, name, handle_func): def search_game(self, name, handle_func):
payload = { payload = {
"query": search_query, "query": search_query,
"variables": {"category": "games/edition/base|bundles/games|editors|software/edition/base", "count": 10, "variables": {
"country": self.country_code, "keywords": name, "locale": self.locale, "sortDir": "DESC", "category": "games/edition/base|bundles/games|editors|software/edition/base",
"allowCountries": self.country_code, "count": 10,
"start": 0, "tag": "", "withMapping": False, "withPrice": True} "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): def _handle_search(self, data, handle_func):
try: try:
@ -98,13 +117,26 @@ class ShopApiCore(QObject):
url = "https://www.epicgames.com/graphql?operationName=searchStoreQuery&variables=" url = "https://www.epicgames.com/graphql?operationName=searchStoreQuery&variables="
args = urllib.parse.quote_plus(str(browse_model.__dict__)) args = urllib.parse.quote_plus(str(browse_model.__dict__))
for old, new in [("%27", "%22"), ("+", ""), ("%3A", ":"), ("%2C", ","), ("%5B", "["), ("%5D", "]"), for old, new in [
("True", "true")]: ("%27", "%22"),
("+", ""),
("%3A", ":"),
("%2C", ","),
("%5B", "["),
("%5D", "]"),
("True", "true"),
]:
args = args.replace(old, new) 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): def _handle_browse_games(self, data, handle_func):
self.browse_active = False self.browse_active = False
@ -143,11 +175,15 @@ class ShopApiCore(QObject):
"offerId": offer_id, "offerId": offer_id,
"namespace": namespace, "namespace": namespace,
"country": self.country_code, "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): def _handle_add_to_wishlist(self, data, handle_func):
try: try:
@ -166,11 +202,15 @@ class ShopApiCore(QObject):
"variables": { "variables": {
"offerId": offer_id, "offerId": offer_id,
"namespace": namespace, "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): def _handle_remove_from_wishlist(self, data, handle_func):
try: try:

View file

@ -3,9 +3,15 @@ from dataclasses import dataclass
class ImageUrlModel: class ImageUrlModel:
def __init__(self, front_tall: str = "", offer_image_tall: str = "", def __init__(
thumbnail: str = "", front_wide: str = "", offer_image_wide: str = "", self,
product_logo: str = ""): 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.front_tall = front_tall
self.offer_image_tall = offer_image_tall self.offer_image_tall = offer_image_tall
self.thumbnail = thumbnail self.thumbnail = thumbnail
@ -34,17 +40,30 @@ class ImageUrlModel:
class ShopGame: class ShopGame:
# TODO: Copyrights etc # TODO: Copyrights etc
def __init__(self, title: str = "", image_urls: ImageUrlModel = None, social_links: dict = None, def __init__(
langs: list = None, reqs: dict = None, publisher: str = "", developer: str = "", self,
original_price: str = "", discount_price: str = "", tags: list = None, namespace: str = "", title: str = "",
offer_id: 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.title = title
self.image_urls = image_urls self.image_urls = image_urls
self.links = [] self.links = []
if social_links: if social_links:
for item in social_links: for item in social_links:
if item.startswith("link"): 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: else:
self.links = [] self.links = []
self.languages = langs self.languages = langs
@ -77,7 +96,9 @@ class ShopGame:
for item in links: for item in links:
if item.startswith("link"): if item.startswith("link"):
tmp.links.append(tuple((item.replace("link", ""), links[item]))) 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 = {} tmp.reqs = {}
for i, system in enumerate(api_data["data"]["requirements"].get("systems", [])): for i, system in enumerate(api_data["data"]["requirements"].get("systems", [])):
try: try:
@ -86,7 +107,10 @@ class ShopGame:
continue continue
for req in system["details"]: for req in system["details"]:
try: try:
tmp.reqs[system["systemType"]][req["title"]] = (req["minimum"], req["recommended"]) tmp.reqs[system["systemType"]][req["title"]] = (
req["minimum"],
req["recommended"],
)
except KeyError: except KeyError:
pass pass
tmp.publisher = api_data["data"]["meta"].get("publisher", "") tmp.publisher = api_data["data"]["meta"].get("publisher", "")
@ -95,9 +119,14 @@ class ShopGame:
for i in search_data["customAttributes"]: for i in search_data["customAttributes"]:
if i["key"] == "developerName": if i["key"] == "developerName":
tmp.developer = i["value"] tmp.developer = i["value"]
tmp.price = search_data['price']['totalPrice']['fmtPrice']['originalPrice'] tmp.price = search_data["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
tmp.discount_price = search_data['price']['totalPrice']['fmtPrice']['discountPrice'] tmp.discount_price = search_data["price"]["totalPrice"]["fmtPrice"][
tmp.tags = [i.replace("_", " ").capitalize() for i in api_data["data"]["meta"].get("tags", [])] "discountPrice"
]
tmp.tags = [
i.replace("_", " ").capitalize()
for i in api_data["data"]["meta"].get("tags", [])
]
tmp.namespace = search_data["namespace"] tmp.namespace = search_data["namespace"]
tmp.offer_id = search_data["id"] tmp.offer_id = search_data["id"]
@ -116,7 +145,9 @@ class BrowseModel:
tag: str = "" tag: str = ""
withMapping: bool = True withMapping: bool = True
withPrice: 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 = "" price: str = ""
onSale: bool = False onSale: bool = False

View file

@ -3,7 +3,14 @@ import logging
import random import random
from PyQt5.QtCore import pyqtSignal 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 legendary.core import LegendaryCore
from rare.ui.components.tabs.store.store import Ui_ShopWidget 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.addWidget(WaitingSpinner())
self.game_stack.setCurrentIndex(1) 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.layout().insertWidget(0, self.search_bar)
# self.search_bar.textChanged.connect(self.search_games) # self.search_bar.textChanged.connect(self.search_games)
@ -78,10 +87,14 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
if item: if item:
item.widget().deleteLater() item.widget().deleteLater()
if wishlist and wishlist[0] == "error": 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")) btn = QPushButton(self.tr("Reload"))
self.discount_widget.layout().addWidget(btn) 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) self.discount_stack.setCurrentIndex(0)
return return
@ -109,10 +122,14 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
item.widget().deleteLater() item.widget().deleteLater()
if free_games and free_games[0] == "error": 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")) btn = QPushButton(self.tr("Reload"))
self.free_widget.layout().addWidget(btn) 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) self.free_stack.setCurrentIndex(0)
return return
@ -129,9 +146,11 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
coming_free_games = [] coming_free_games = []
for game in free_games: for game in free_games:
try: try:
if game['price']['totalPrice']['fmtPrice']['discountPrice'] == "0" and \ if (
game['price']['totalPrice']['fmtPrice']['originalPrice'] != \ game["price"]["totalPrice"]["fmtPrice"]["discountPrice"] == "0"
game['price']['totalPrice']['fmtPrice']['discountPrice']: and game["price"]["totalPrice"]["fmtPrice"]["originalPrice"]
!= game["price"]["totalPrice"]["fmtPrice"]["discountPrice"]
):
free_games_now.append(game) free_games_now.append(game)
continue continue
@ -145,13 +164,19 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
# parse datetime to check if game is next week or now # parse datetime to check if game is next week or now
try: try:
start_date = datetime.datetime.strptime( start_date = datetime.datetime.strptime(
game["promotions"]["upcomingPromotionalOffers"][0]["promotionalOffers"][0]["startDate"], game["promotions"]["upcomingPromotionalOffers"][0][
'%Y-%m-%dT%H:%M:%S.%fZ') "promotionalOffers"
][0]["startDate"],
"%Y-%m-%dT%H:%M:%S.%fZ",
)
except Exception: except Exception:
try: try:
start_date = datetime.datetime.strptime( start_date = datetime.datetime.strptime(
game["promotions"]["promotionalOffers"][0]["promotionalOffers"][0]["startDate"], game["promotions"]["promotionalOffers"][0][
'%Y-%m-%dT%H:%M:%S.%fZ') "promotionalOffers"
][0]["startDate"],
"%Y-%m-%dT%H:%M:%S.%fZ",
)
except Exception as e: except Exception as e:
continue continue
@ -171,7 +196,9 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
self.free_game_widgets.append(w) self.free_game_widgets.append(w)
now_free += 1 now_free += 1
if now_free == 0: 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 # free games next week
for free_game in coming_free_games: for free_game in coming_free_games:
@ -187,30 +214,53 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
def init_filter(self): def init_filter(self):
self.none_price.toggled.connect(lambda: self.prepare_request("") if self.none_price.isChecked() else None) self.none_price.toggled.connect(
self.free_button.toggled.connect(lambda: self.prepare_request("free") if self.free_button.isChecked() else None) 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( 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( 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( self.under30.toggled.connect(
lambda: self.prepare_request("<price>[0, 3000)") if self.under30.isChecked() else None) lambda: self.prepare_request("<price>[0, 3000)")
self.above.toggled.connect(lambda: self.prepare_request("<price>[1499,]") if self.above.isChecked() else None) 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("sale") if self.on_discount.isChecked() else None)
self.on_discount.toggled.connect(lambda: self.prepare_request()) self.on_discount.toggled.connect(lambda: self.prepare_request())
constants = Constants() constants = Constants()
self.checkboxes = [] self.checkboxes = []
for groupbox, variables in [(self.genre_gb, constants.categories), for groupbox, variables in [
(self.platform_gb, constants.platforms), (self.genre_gb, constants.categories),
(self.others_gb, constants.others), (self.platform_gb, constants.platforms),
(self.type_gb, constants.types)]: (self.others_gb, constants.others),
(self.type_gb, constants.types),
]:
for text, tag in variables: for text, tag in variables:
checkbox = CheckBox(text, tag) checkbox = CheckBox(text, tag)
checkbox.activated.connect(lambda x: self.prepare_request(added_tag=x)) 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) groupbox.layout().addWidget(checkbox)
self.checkboxes.append(checkbox) self.checkboxes.append(checkbox)
self.reset_button.clicked.connect(self.reset_filters) self.reset_button.clicked.connect(self.reset_filters)
@ -228,8 +278,14 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
self.on_discount.setChecked(False) self.on_discount.setChecked(False)
def prepare_request(self, price: str = None, added_tag: int = 0, removed_tag: int = 0, def prepare_request(
added_type: str = "", removed_type: str = ""): self,
price: str = None,
added_tag: int = 0,
removed_tag: int = 0,
added_type: str = "",
removed_type: str = "",
):
if not self.update_games_allowed: if not self.update_games_allowed:
return return
if price is not None: if price is not None:
@ -255,9 +311,14 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
self.game_stack.setCurrentIndex(1) self.game_stack.setCurrentIndex(1)
date = f"[{datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%d')},]" 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, browse_model = BrowseModel(
date=date, count=20, price=self.price, language_code=self.core.language_code,
onSale=self.on_discount.isChecked()) country_code=self.core.country_code,
date=date,
count=20,
price=self.price,
onSale=self.on_discount.isChecked(),
)
browse_model.tag = "|".join(self.tags) browse_model.tag = "|".join(self.tags)
if self.types: if self.types:
@ -265,7 +326,10 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
self.api_core.browse_games(browse_model, self.show_games) self.api_core.browse_games(browse_model, self.show_games)
def show_games(self, data): 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() item.widget().deleteLater()
if data: if data:
for game in data: for game in data:
@ -275,7 +339,8 @@ class ShopWidget(QScrollArea, Ui_ShopWidget):
else: else:
self.game_widget.layout().addWidget( 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_stack.setCurrentIndex(0)
self.game_widget.layout().update() self.game_widget.layout().update()

View file

@ -21,22 +21,31 @@ class Wishlist(QStackedWidget, Ui_Wishlist):
self.wishlist = [] self.wishlist = []
self.widgets = [] 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.filter_cb.currentIndexChanged.connect(self.set_filter)
self.reload_button.clicked.connect(self.update_wishlist) self.reload_button.clicked.connect(self.update_wishlist)
self.reload_button.setIcon(icon("fa.refresh", color="white")) 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): def update_wishlist(self):
self.setCurrentIndex(1) self.setCurrentIndex(1)
self.api_core.get_wishlist(self.set_wishlist) self.api_core.get_wishlist(self.set_wishlist)
def delete_from_wishlist(self, game): def delete_from_wishlist(self, game):
self.api_core.remove_from_wishlist(game["namespace"], game["id"], self.api_core.remove_from_wishlist(
lambda success: self.update_wishlist() if success else game["namespace"],
QMessageBox.warning(self, "Error", game["id"],
self.tr("Could not remove game from wishlist"))) 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() self.update_wishlist_signal.emit()
def set_filter(self, i): def set_filter(self, i):
@ -69,14 +78,26 @@ class Wishlist(QStackedWidget, Ui_Wishlist):
if sort == 0: if sort == 0:
sorted_list = sorted(self.wishlist, key=lambda x: x["offer"]["title"]) sorted_list = sorted(self.wishlist, key=lambda x: x["offer"]["title"])
elif sort == 1: elif sort == 1:
sorted_list = sorted(self.wishlist, sorted_list = sorted(
key=lambda x: x["offer"]['price']['totalPrice']['fmtPrice']['discountPrice']) self.wishlist,
key=lambda x: x["offer"]["price"]["totalPrice"]["fmtPrice"][
"discountPrice"
],
)
elif sort == 2: 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: elif sort == 3:
sorted_list = sorted(self.wishlist, reverse=True, key=lambda x: 1 - ( sorted_list = sorted(
x["offer"]["price"]["totalPrice"]["discountPrice"] / x["offer"]["price"]["totalPrice"][ self.wishlist,
"originalPrice"])) reverse=True,
key=lambda x: 1
- (
x["offer"]["price"]["totalPrice"]["discountPrice"]
/ x["offer"]["price"]["totalPrice"]["originalPrice"]
),
)
else: else:
sorted_list = self.wishlist sorted_list = self.wishlist
self.widgets.clear() self.widgets.clear()

View file

@ -9,7 +9,7 @@ class MainTabBar(QTabBar):
self._expanded = expanded self._expanded = expanded
self.setObjectName("MainTabBar") self.setObjectName("MainTabBar")
font = self.font() font = self.font()
font.setPointSize(font.pointSize()+2) font.setPointSize(font.pointSize() + 2)
font.setBold(True) font.setBold(True)
self.setFont(font) self.setFont(font)
# self.setContentsMargins(0,10,0,10) # self.setContentsMargins(0,10,0,10)

View file

@ -2,11 +2,36 @@ import os
from logging import getLogger from logging import getLogger
from typing import Callable, Tuple 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.QtGui import QMovie, QPixmap, QFontMetrics, QImage
from PyQt5.QtWidgets import QLayout, QStyle, QSizePolicy, QLabel, QFileDialog, QHBoxLayout, QWidget, QPushButton, \ from PyQt5.QtWidgets import (
QStyleOptionTab, QStylePainter, QTabBar, QLineEdit, QToolButton, QTabWidget, QCompleter, QFileSystemModel, \ QLayout,
QStyledItemDelegate, QFileIconProvider 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 qtawesome import icon as qta_icon
from rare import cache_dir from rare import cache_dir
@ -33,15 +58,13 @@ class FlowLayout(QLayout):
if self._hspacing >= 0: if self._hspacing >= 0:
return self._hspacing return self._hspacing
else: else:
return self.smartSpacing( return self.smartSpacing(QStyle.PM_LayoutHorizontalSpacing)
QStyle.PM_LayoutHorizontalSpacing)
def verticalSpacing(self): def verticalSpacing(self):
if self._vspacing >= 0: if self._vspacing >= 0:
return self._vspacing return self._vspacing
else: else:
return self.smartSpacing( return self.smartSpacing(QStyle.PM_LayoutVerticalSpacing)
QStyle.PM_LayoutVerticalSpacing)
def count(self): def count(self):
return len(self._items) return len(self._items)
@ -91,13 +114,13 @@ class FlowLayout(QLayout):
hspace = self.horizontalSpacing() hspace = self.horizontalSpacing()
if hspace == -1: if hspace == -1:
hspace = widget.style().layoutSpacing( hspace = widget.style().layoutSpacing(
QSizePolicy.PushButton, QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal
QSizePolicy.PushButton, Qt.Horizontal) )
vspace = self.verticalSpacing() vspace = self.verticalSpacing()
if vspace == -1: if vspace == -1:
vspace = widget.style().layoutSpacing( vspace = widget.style().layoutSpacing(
QSizePolicy.PushButton, QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical
QSizePolicy.PushButton, Qt.Vertical) )
nextX = x + item.sizeHint().width() + hspace nextX = x + item.sizeHint().width() + hspace
if nextX - hspace > effective.right() and lineheight > 0: if nextX - hspace > effective.right() and lineheight > 0:
x = effective.x() x = effective.x()
@ -105,8 +128,7 @@ class FlowLayout(QLayout):
nextX = x + item.sizeHint().width() + hspace nextX = x + item.sizeHint().width() + hspace
lineheight = 0 lineheight = 0
if not testonly: if not testonly:
item.setGeometry( item.setGeometry(QRect(QPoint(x, y), item.sizeHint()))
QRect(QPoint(x, y), item.sizeHint()))
x = nextX x = nextX
lineheight = max(lineheight, item.sizeHint().height()) lineheight = max(lineheight, item.sizeHint().height())
return y + lineheight - rect.y() + bottom return y + lineheight - rect.y() + bottom
@ -125,14 +147,16 @@ class IndicatorLineEdit(QWidget):
textChanged = pyqtSignal(str) textChanged = pyqtSignal(str)
is_valid = False is_valid = False
def __init__(self, def __init__(
text: str = "", self,
ph_text: str = "", text: str = "",
completer: QCompleter = None, ph_text: str = "",
edit_func: Callable[[str], Tuple[bool, str]] = None, completer: QCompleter = None,
save_func: Callable[[str], None] = None, edit_func: Callable[[str], Tuple[bool, str]] = None,
horiz_policy: QSizePolicy = QSizePolicy.Expanding, save_func: Callable[[str], None] = None,
parent=None): horiz_policy: QSizePolicy = QSizePolicy.Expanding,
parent=None,
):
super(IndicatorLineEdit, self).__init__(parent=parent) super(IndicatorLineEdit, self).__init__(parent=parent)
self.setObjectName("IndicatorLineEdit") self.setObjectName("IndicatorLineEdit")
self.layout = QHBoxLayout(self) self.layout = QHBoxLayout(self)
@ -146,7 +170,7 @@ class IndicatorLineEdit(QWidget):
# Add hint_label to line_edit # Add hint_label to line_edit
self.line_edit.setLayout(QHBoxLayout()) self.line_edit.setLayout(QHBoxLayout())
self.hint_label = QLabel() self.hint_label = QLabel()
self.hint_label.setObjectName('HintLabel') self.hint_label.setObjectName("HintLabel")
self.hint_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.hint_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.line_edit.layout().setContentsMargins(0, 0, 10, 0) self.line_edit.layout().setContentsMargins(0, 0, 10, 0)
self.line_edit.layout().addWidget(self.hint_label) self.line_edit.layout().addWidget(self.hint_label)
@ -158,13 +182,17 @@ class IndicatorLineEdit(QWidget):
self.layout.addWidget(self.line_edit) self.layout.addWidget(self.line_edit)
if edit_func is not None: if edit_func is not None:
self.indicator_label = QLabel() 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.indicator_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.layout.addWidget(self.indicator_label) self.layout.addWidget(self.indicator_label)
if not ph_text: if not ph_text:
_translate = QCoreApplication.translate _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.edit_func = edit_func
self.save_func = save_func self.save_func = save_func
@ -193,7 +221,9 @@ class IndicatorLineEdit(QWidget):
def __indicator(self, res): def __indicator(self, res):
color = "green" if res else "red" 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): def __edit(self, text):
if self.edit_func is not None: if self.edit_func is not None:
@ -214,22 +244,22 @@ class IndicatorLineEdit(QWidget):
class PathEditIconProvider(QFileIconProvider): class PathEditIconProvider(QFileIconProvider):
icons = [ icons = [
'mdi.file-cancel', # Unknown "mdi.file-cancel", # Unknown
'mdi.desktop-classic', # Computer "mdi.desktop-classic", # Computer
'mdi.desktop-mac', # Desktop "mdi.desktop-mac", # Desktop
'mdi.trash-can', # Trashcan "mdi.trash-can", # Trashcan
'mdi.server-network', # Network "mdi.server-network", # Network
'mdi.harddisk', # Drive "mdi.harddisk", # Drive
'mdi.folder', # Folder "mdi.folder", # Folder
'mdi.file', # File "mdi.file", # File
'mdi.cog', # Executable "mdi.cog", # Executable
] ]
def __init__(self): def __init__(self):
super(PathEditIconProvider, self).__init__() super(PathEditIconProvider, self).__init__()
self.icon_types = dict() self.icon_types = dict()
for idx, icn in enumerate(self.icons): 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): def icon(self, info_type):
if isinstance(info_type, QFileInfo): if isinstance(info_type, QFileInfo):
@ -249,25 +279,35 @@ class PathEdit(IndicatorLineEdit):
completer = QCompleter() completer = QCompleter()
compl_model = QFileSystemModel() compl_model = QFileSystemModel()
def __init__(self, def __init__(
path: str = "", self,
file_type: QFileDialog.FileType = QFileDialog.AnyFile, path: str = "",
type_filter: str = "", file_type: QFileDialog.FileType = QFileDialog.AnyFile,
name_filter: str = "", type_filter: str = "",
ph_text: str = "", name_filter: str = "",
edit_func: Callable[[str], Tuple[bool, str]] = None, ph_text: str = "",
save_func: Callable[[str], None] = None, edit_func: Callable[[str], Tuple[bool, str]] = None,
horiz_policy: QSizePolicy = QSizePolicy.Expanding, save_func: Callable[[str], None] = None,
parent=None): horiz_policy: QSizePolicy = QSizePolicy.Expanding,
self.compl_model.setOptions(QFileSystemModel.DontWatchForChanges | parent=None,
QFileSystemModel.DontResolveSymlinks | ):
QFileSystemModel.DontUseCustomDirectoryIcons) self.compl_model.setOptions(
QFileSystemModel.DontWatchForChanges
| QFileSystemModel.DontResolveSymlinks
| QFileSystemModel.DontUseCustomDirectoryIcons
)
self.compl_model.setIconProvider(PathEditIconProvider()) self.compl_model.setIconProvider(PathEditIconProvider())
self.compl_model.setRootPath(path) self.compl_model.setRootPath(path)
self.completer.setModel(self.compl_model) self.completer.setModel(self.compl_model)
super(PathEdit, self).__init__(text=path, ph_text=ph_text, completer=self.completer, super(PathEdit, self).__init__(
edit_func=edit_func, save_func=save_func, text=path,
horiz_policy=horiz_policy, parent=parent) 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.setObjectName("PathEdit")
self.line_edit.setMinimumSize(QSize(300, 0)) self.line_edit.setMinimumSize(QSize(300, 0))
self.path_select = QToolButton(self) self.path_select = QToolButton(self)
@ -353,10 +393,12 @@ class SideTabWidget(QTabWidget):
class WaitingSpinner(QLabel): class WaitingSpinner(QLabel):
def __init__(self): def __init__(self):
super(WaitingSpinner, self).__init__() super(WaitingSpinner, self).__init__()
self.setStyleSheet(""" self.setStyleSheet(
"""
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
""") """
)
self.movie = QMovie(":/images/loader.gif") self.movie = QMovie(":/images/loader.gif")
self.setMovie(self.movie) self.setMovie(self.movie)
self.movie.start() self.movie.start()
@ -368,11 +410,15 @@ class SelectViewWidget(QWidget):
def __init__(self, icon_view: bool): def __init__(self, icon_view: bool):
super(SelectViewWidget, self).__init__() super(SelectViewWidget, self).__init__()
self.icon_view = icon_view 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.icon_view_button = QPushButton()
self.list_view = QPushButton() self.list_view = QPushButton()
if icon_view: 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")) self.list_view.setIcon(qta_icon("fa5s.list"))
else: else:
self.icon_view_button.setIcon(qta_icon("mdi.view-grid-outline")) self.icon_view_button.setIcon(qta_icon("mdi.view-grid-outline"))
@ -438,14 +484,19 @@ class ImageLabel(QLabel):
return return
image = QImage() image = QImage()
image.loadFromData(data) 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) pixmap = QPixmap().fromImage(image)
self.setPixmap(pixmap) self.setPixmap(pixmap)
def show_image(self): def show_image(self):
self.image = QPixmap(os.path.join(self.path, self.name)).scaled(*self.img_size, self.image = QPixmap(os.path.join(self.path, self.name)).scaled(
transformMode=Qt.SmoothTransformation) *self.img_size, transformMode=Qt.SmoothTransformation
)
self.setPixmap(self.image) self.setPixmap(self.image)
@ -457,20 +508,31 @@ class ButtonLineEdit(QLineEdit):
self.button = QToolButton(self) self.button = QToolButton(self)
self.button.setIcon(qta_icon(icon_name, color="white")) 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.setCursor(Qt.ArrowCursor)
self.button.clicked.connect(self.buttonClicked.emit) self.button.clicked.connect(self.buttonClicked.emit)
self.setPlaceholderText(placeholder_text) self.setPlaceholderText(placeholder_text)
frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth) frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
buttonSize = self.button.sizeHint() buttonSize = self.button.sizeHint()
self.setStyleSheet('QLineEdit {padding-right: %dpx; }' % (buttonSize.width() + frameWidth + 1)) self.setStyleSheet(
self.setMinimumSize(max(self.minimumSizeHint().width(), buttonSize.width() + frameWidth * 2 + 2), "QLineEdit {padding-right: %dpx; }" % (buttonSize.width() + frameWidth + 1)
max(self.minimumSizeHint().height(), buttonSize.height() + frameWidth * 2 + 2)) )
self.setMinimumSize(
max(
self.minimumSizeHint().width(), buttonSize.width() + frameWidth * 2 + 2
),
max(
self.minimumSizeHint().height(),
buttonSize.height() + frameWidth * 2 + 2,
),
)
def resizeEvent(self, event): def resizeEvent(self, event):
buttonSize = self.button.sizeHint() buttonSize = self.button.sizeHint()
frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth) frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
self.button.move(self.rect().right() - frameWidth - buttonSize.width(), self.button.move(
(self.rect().bottom() - buttonSize.height() + 1) // 2) self.rect().right() - frameWidth - buttonSize.width(),
(self.rect().bottom() - buttonSize.height() + 1) // 2,
)
super(ButtonLineEdit, self).resizeEvent(event) super(ButtonLineEdit, self).resizeEvent(event)

View file

@ -64,10 +64,7 @@ class QJsonTreeItem(object):
return len(self._children) return len(self._children)
def row(self): def row(self):
return ( return self._parent._children.index(self) if self._parent else 0
self._parent._children.index(self)
if self._parent else 0
)
@property @property
def key(self): def key(self):
@ -99,10 +96,7 @@ class QJsonTreeItem(object):
rootItem.key = "root" rootItem.key = "root"
if isinstance(value, dict): if isinstance(value, dict):
items = ( items = sorted(value.items()) if sort else value.items()
sorted(value.items())
if sort else value.items()
)
for key, value in items: for key, value in items:
child = self.load(value, rootItem) child = self.load(value, rootItem)
@ -142,10 +136,9 @@ class QJsonModel(QtCore.QAbstractItemModel):
""" """
assert isinstance(document, (dict, list, tuple)), ( assert isinstance(
"`document` must be of dict, list or tuple, " document, (dict, list, tuple)
"not %s" % type(document) ), "`document` must be of dict, list or tuple, " "not %s" % type(document)
)
self.beginResetModel() self.beginResetModel()
@ -277,7 +270,7 @@ class QJsonModel(QtCore.QAbstractItemModel):
return item.value return item.value
if __name__ == '__main__': if __name__ == "__main__":
import sys import sys
app = QtWidgets.QApplication(sys.argv) app = QtWidgets.QApplication(sys.argv)
@ -286,7 +279,8 @@ if __name__ == '__main__':
view.setModel(model) view.setModel(model)
document = json.loads("""\ document = json.loads(
"""\
{ {
"firstName": "John", "firstName": "John",
"lastName": "Smith", "lastName": "Smith",
@ -308,16 +302,16 @@ if __name__ == '__main__':
} }
] ]
} }
""") """
)
model.load(document) model.load(document)
model.clear() model.clear()
model.load(document) model.load(document)
# Sanity check # Sanity check
assert ( assert json.dumps(model.json(), sort_keys=True) == json.dumps(
json.dumps(model.json(), sort_keys=True) == document, sort_keys=True
json.dumps(document, sort_keys=True)
) )
view.show() view.show()

View file

@ -22,15 +22,28 @@ def uninstall(app_name: str, core: LegendaryCore, options=None):
if platform.system() == "Linux": if platform.system() == "Linux":
if os.path.exists(os.path.expanduser(f"~/Desktop/{igame.title}.desktop")): if os.path.exists(os.path.expanduser(f"~/Desktop/{igame.title}.desktop")):
os.remove(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")): if os.path.exists(
os.remove(os.path.expanduser(f"~/.local/share/applications/{igame.title}.desktop")) 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": 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")) os.remove(os.path.expanduser(f"~/Desktop/{igame.title.split(':')[0]}.lnk"))
elif os.path.exists( elif os.path.exists(
os.path.expandvars(f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk")): os.path.expandvars(
os.remove(os.path.expandvars(f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk")) 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: try:
# Remove DLC first so directory is empty when game uninstall runs # 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"]) core.uninstall_game(idlc, delete_files=not options["keep_files"])
logger.info(f'Removing "{igame.title}" from "{igame.install_path}"...') logger.info(f'Removing "{igame.title}" from "{igame.install_path}"...')
core.uninstall_game(igame, delete_files=not options["keep_files"], delete_root_directory=True) core.uninstall_game(
logger.info('Game has been uninstalled.') igame, delete_files=not options["keep_files"], delete_root_directory=True
)
logger.info("Game has been uninstalled.")
if not options["keep_files"]: if not options["keep_files"]:
shutil.rmtree(igame.install_path) shutil.rmtree(igame.install_path)
except Exception as e: 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") logger.info("Removing sections in config file")
if core.lgd.config.has_section(app_name): 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): def update_manifest(app_name: str, core: LegendaryCore):
game = core.get_game(app_name) 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) new_manifest_data, base_urls = core.get_cdn_manifest(game)
# overwrite base urls in metadata with current ones to avoid using old/dead CDNs # overwrite base urls in metadata with current ones to avoid using old/dead CDNs
game.base_urls = base_urls 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) core.lgd.set_game_meta(game.app_name, game)
new_manifest = core.load_manifest(new_manifest_data) 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. # save manifest with version name as well for testing/downgrading/etc.
core.lgd.save_manifest(game.app_name, new_manifest_data, core.lgd.save_manifest(
version=new_manifest.meta.build_version) game.app_name, new_manifest_data, version=new_manifest.meta.build_version
)
class VerifySignals(QObject): class VerifySignals(QObject):
@ -105,8 +123,9 @@ class VerifyWorker(QRunnable):
manifest = self.core.load_manifest(manifest_data) manifest = self.core.load_manifest(manifest_data)
files = sorted(manifest.file_manifest_list.elements, files = sorted(
key=lambda a: a.filename.lower()) manifest.file_manifest_list.elements, key=lambda a: a.filename.lower()
)
# build list of hashes # build list of hashes
file_list = [(f.filename, f.sha_hash.hex()) for f in files] file_list = [(f.filename, f.sha_hash.hex()) for f in files]
@ -117,46 +136,60 @@ class VerifyWorker(QRunnable):
_translate = QCoreApplication.translate _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 = [] repair_file = []
try: 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.signals.status.emit((self.num, self.total, self.app_name))
self.num += 1 self.num += 1
if result == VerifyResult.HASH_MATCH: if result == VerifyResult.HASH_MATCH:
repair_file.append(f'{result_hash}:{path}') repair_file.append(f"{result_hash}:{path}")
continue continue
elif result == VerifyResult.HASH_MISMATCH: elif result == VerifyResult.HASH_MISMATCH:
logger.error(f'File does not match hash: "{path}"') 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) failed.append(path)
elif result == VerifyResult.FILE_MISSING: elif result == VerifyResult.FILE_MISSING:
logger.error(f'File is missing: "{path}"') logger.error(f'File is missing: "{path}"')
missing.append(path) missing.append(path)
else: 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) missing.append(path)
except OSError as e: 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)) logger.error(str(e))
except ValueError as 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)) logger.error(str(e))
# always write repair file, even if all match # always write repair file, even if all match
if repair_file: if repair_file:
repair_filename = os.path.join(self.core.lgd.get_tmp_path(), f'{self.app_name}.repair') repair_filename = os.path.join(
with open(repair_filename, 'w') as f: self.core.lgd.get_tmp_path(), f"{self.app_name}.repair"
f.write('\n'.join(repair_file)) )
with open(repair_filename, "w") as f:
f.write("\n".join(repair_file))
logger.debug(f'Written repair file to "{repair_filename}"') logger.debug(f'Written repair file to "{repair_filename}"')
if not missing and not failed: 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) self.signals.summary.emit(0, 0, self.app_name)
else: 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) 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") return _tr("LgdUtils", "Path does not exist")
manifest, igame = core.import_game(game, path) 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): if not os.path.exists(exe_path):
logger.error(f"Launch Executable of {game.app_title} does not exist") 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: if game.is_dlc:
release_info = game.metadata.get('mainGameItem', {}).get('releaseInfo') release_info = game.metadata.get("mainGameItem", {}).get("releaseInfo")
if release_info: if release_info:
main_game_appname = release_info[0]['appId'] main_game_appname = release_info[0]["appId"]
main_game_title = game.metadata['mainGameItem']['title'] main_game_title = game.metadata["mainGameItem"]["title"]
if not core.is_installed(main_game_appname): 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: else:
return _tr("LgdUtils", "Unable to get base game information for DLC") return _tr("LgdUtils", "Unable to get base game information for DLC")
total = len(manifest.file_manifest_list.elements) total = len(manifest.file_manifest_list.elements)
found = sum(os.path.exists(os.path.join(path, f.filename)) found = sum(
for f in manifest.file_manifest_list.elements) os.path.exists(os.path.join(path, f.filename))
for f in manifest.file_manifest_list.elements
)
ratio = found / total ratio = found / total
if ratio < 0.9: if ratio < 0.9:
logger.warning( 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 # return False
core.install_game(igame) core.install_game(igame)
if igame.needs_verification: if igame.needs_verification:

View file

@ -20,7 +20,7 @@ class InstallOptionsModel:
no_install: bool = False no_install: bool = False
ignore_space_req: bool = False ignore_space_req: bool = False
force: bool = False force: bool = False
sdl_list: list = field(default_factory=lambda: ['']) sdl_list: list = field(default_factory=lambda: [""])
update: bool = False update: bool = False
silent: bool = False silent: bool = False
platform: str = "" platform: str = ""
@ -47,32 +47,38 @@ class InstallQueueItemModel:
options: InstallOptionsModel = None options: InstallOptionsModel = None
def __bool__(self): 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: class PathSpec:
__egl_path_vars = { __egl_path_vars = {
'{appdata}': os.path.expandvars('%LOCALAPPDATA%'), "{appdata}": os.path.expandvars("%LOCALAPPDATA%"),
'{userdir}': os.path.expandvars('%USERPROFILE%/Documents'), "{userdir}": os.path.expandvars("%USERPROFILE%/Documents"),
# '{userprofile}': os.path.expandvars('%userprofile%'), # possibly wrong # '{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_appdata: str = r"%LOCALAPPDATA%\EpicGamesLauncher\Saved\Config\Windows"
egl_programdata: str = r'%PROGRAMDATA%\Epic\EpicGamesLauncher\Data\Manifests' egl_programdata: str = r"%PROGRAMDATA%\Epic\EpicGamesLauncher\Data\Manifests"
wine_programdata: str = r'dosdevices/c:/ProgramData' 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: 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 self.app_name = app_name
def cook(self, path: str) -> str: 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) return os.path.join(*cooked_path)
@property @property
def wine_egl_programdata(self): 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]: def wine_egl_prefixes(self, results: int = 0) -> Union[List[str], str]:
possible_prefixes = [ possible_prefixes = [
@ -103,12 +109,14 @@ class ApiResults:
saves: list = None saves: list = None
def __bool__(self): def __bool__(self):
return self.game_list is not None \ return (
and self.dlcs is not None \ self.game_list is not None
and self.bit32_games is not None \ and self.dlcs is not None
and self.mac_games is not None \ and self.bit32_games is not None
and self.no_asset_games is not None \ and self.mac_games is not None
and self.saves is not None and self.no_asset_games is not None
and self.saves is not None
)
class Signals(QObject): class Signals(QObject):

View file

@ -29,30 +29,41 @@ class QtRequestManager(QObject):
self.request_active = RequestQueueItem(handle_func=handle_func) self.request_active = RequestQueueItem(handle_func=handle_func)
payload = json.dumps(payload).encode("utf-8") payload = json.dumps(payload).encode("utf-8")
request.setHeader(QNetworkRequest.UserAgentHeader, request.setHeader(
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36") 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: 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 = self.manager.post(request, payload)
self.request.finished.connect(self.prepare_data) self.request.finished.connect(self.prepare_data)
else: else:
self.request_queue.append( 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]): def get(self, url: str, handle_func: Callable[[Union[dict, bytes]], None]):
if not self.request_active: if not self.request_active:
request = QNetworkRequest(QUrl(url)) request = QNetworkRequest(QUrl(url))
request.setHeader(QNetworkRequest.UserAgentHeader, request.setHeader(
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36") 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_active = RequestQueueItem(handle_func=handle_func)
self.request = self.manager.get(request) self.request = self.manager.get(request)
self.request.finished.connect(self.prepare_data) self.request.finished.connect(self.prepare_data)
else: 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): def prepare_data(self):
# self.request_active = False # self.request_active = False
@ -62,7 +73,9 @@ class QtRequestManager(QObject):
if self.request.error() == QNetworkReply.NoError: if self.request.error() == QNetworkReply.NoError:
if self.type == "json": if self.type == "json":
error = QJsonParseError() 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: if QJsonParseError.NoError == error.error:
data = json.loads(json_data.toJson().data().decode()) data = json.loads(json_data.toJson().data().decode())
else: else:
@ -78,7 +91,11 @@ class QtRequestManager(QObject):
if self.request_queue: if self.request_queue:
if self.request_queue[0].method == "post": 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: else:
self.get(self.request_queue[0].url, self.request_queue[0].handle_func) self.get(self.request_queue[0].url, self.request_queue[0].handle_func)
self.request_queue.pop(0) self.request_queue.pop(0)

View file

@ -61,7 +61,9 @@ class DiscordRPC(QObject):
def set_discord_rpc(self, app_name=None): def set_discord_rpc(self, app_name=None):
if not self.RPC: if not self.RPC:
try: 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() self.RPC.connect()
except ConnectionRefusedError as e: except ConnectionRefusedError as e:
logger.warning("Discord is not active\n" + str(e)) logger.warning("Discord is not active\n" + str(e))
@ -83,13 +85,15 @@ class DiscordRPC(QObject):
def update_rpc(self, app_name=None): def update_rpc(self, app_name=None):
if self.settings.value("rpc_enable", 0, int) == 2 or ( 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() self.remove_rpc()
return return
title = None title = None
if not app_name: if not app_name:
self.RPC.update(large_image="logo", self.RPC.update(
details="https://github.com/Dummerle/Rare") large_image="logo", details="https://github.com/Dummerle/Rare"
)
return return
if self.settings.value("rpc_name", True, bool): if self.settings.value("rpc_name", True, bool):
try: try:
@ -104,9 +108,7 @@ class DiscordRPC(QObject):
if self.settings.value("rpc_os", True, bool): if self.settings.value("rpc_os", True, bool):
os = "via Rare on " + platform.system() os = "via Rare on " + platform.system()
self.RPC.update(large_image="logo", self.RPC.update(
details=title, large_image="logo", details=title, large_text=title, state=os, start=start
large_text=title, )
state=os,
start=start)
self.state = 0 self.state = 0

View file

@ -34,36 +34,38 @@ class SingleInstance(object):
if lockfile: if lockfile:
self.lockfile = lockfile self.lockfile = lockfile
else: else:
basename = os.path.splitext(os.path.abspath(sys.argv[0]))[0].replace( basename = (
"/", "-").replace(":", "").replace("\\", "-") + '-%s' % flavor_id + '.lock' os.path.splitext(os.path.abspath(sys.argv[0]))[0]
self.lockfile = os.path.normpath( .replace("/", "-")
tempfile.gettempdir() + '/' + basename) .replace(":", "")
.replace("\\", "-")
+ "-%s" % flavor_id
+ ".lock"
)
self.lockfile = os.path.normpath(tempfile.gettempdir() + "/" + basename)
logger.debug("SingleInstance lockfile: " + self.lockfile) logger.debug("SingleInstance lockfile: " + self.lockfile)
if sys.platform == 'win32': if sys.platform == "win32":
try: try:
# file already exists, we try to remove (in case previous # file already exists, we try to remove (in case previous
# execution was interrupted) # execution was interrupted)
if os.path.exists(self.lockfile): if os.path.exists(self.lockfile):
os.unlink(self.lockfile) os.unlink(self.lockfile)
self.fd = os.open( self.fd = os.open(self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR)
self.lockfile, os.O_CREAT | os.O_EXCL | os.O_RDWR)
except OSError: except OSError:
type, e, tb = sys.exc_info() type, e, tb = sys.exc_info()
if e.errno == 13: if e.errno == 13:
logger.error( logger.error("Another instance is already running, quitting.")
"Another instance is already running, quitting.")
raise SingleInstanceException() raise SingleInstanceException()
print(e.errno) print(e.errno)
raise raise
else: # non Windows else: # non Windows
self.fp = open(self.lockfile, 'w') self.fp = open(self.lockfile, "w")
self.fp.flush() self.fp.flush()
try: try:
fcntl.lockf(self.fp, fcntl.LOCK_EX | fcntl.LOCK_NB) fcntl.lockf(self.fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError: except IOError:
logger.warning( logger.warning("Another instance is already running, quitting.")
"Another instance is already running, quitting.")
raise SingleInstanceException() raise SingleInstanceException()
self.initialized = True self.initialized = True
@ -71,8 +73,8 @@ class SingleInstance(object):
if not self.initialized: if not self.initialized:
return return
try: try:
if sys.platform == 'win32': if sys.platform == "win32":
if hasattr(self, 'fd'): if hasattr(self, "fd"):
os.close(self.fd) os.close(self.fd)
os.unlink(self.lockfile) os.unlink(self.lockfile)
else: else:

View file

@ -34,14 +34,16 @@ class SteamWorker(QRunnable):
"bronze": _tr("SteamWorker", "Bronze"), "bronze": _tr("SteamWorker", "Bronze"),
"fail": _tr("SteamWorker", "Could not get grade"), "fail": _tr("SteamWorker", "Could not get grade"),
"borked": _tr("SteamWorker", "unplayable"), "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): def set_app_name(self, app_name: str):
self.app_name = app_name self.app_name = app_name
def run(self) -> None: 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): def get_rating(app_name: str):
@ -57,10 +59,7 @@ def get_rating(app_name: str):
steam_id = get_steam_id(game.app_title) steam_id = get_steam_id(game.app_title)
grade = get_grade(steam_id) grade = get_grade(steam_id)
grades[app_name] = { grades[app_name] = {"steam_id": steam_id, "grade": grade}
"steam_id": steam_id,
"grade": grade
}
with open(os.path.join(data_dir, "steam_ids.json"), "w") as f: with open(os.path.join(data_dir, "steam_ids.json"), "w") as f:
f.write(json.dumps(grades)) f.write(json.dumps(grades))
f.close() f.close()
@ -74,14 +73,14 @@ def get_grade(steam_code):
if steam_code == 0: if steam_code == 0:
return "fail" return "fail"
steam_code = str(steam_code) steam_code = str(steam_code)
url = 'https://www.protondb.com/api/v1/reports/summaries/' url = "https://www.protondb.com/api/v1/reports/summaries/"
res = requests.get(url + steam_code + '.json') res = requests.get(url + steam_code + ".json")
try: try:
lista = json.loads(res.text) lista = json.loads(res.text)
except json.decoder.JSONDecodeError: except json.decoder.JSONDecodeError:
return "fail" return "fail"
return lista['tier'] return lista["tier"]
def load_json() -> dict: 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 def check_time(): # this function check if it's time to update
global file global file
text = open(file, 'r') text = open(file, "r")
json_table = json.load(text) json_table = json.load(text)
text.close() text.close()
today = date.today() today = date.today()
day = 0 # it controls how many days it's necessary for an update day = 0 # it controls how many days it's necessary for an update
for i in 'ymd': for i in "ymd":
if i == 'd': if i == "d":
day = 7 day = 7
else: else:
day = 0 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 return 1
else: else:
return 0 return 0

View file

@ -10,7 +10,16 @@ from typing import Tuple, List
import qtawesome import qtawesome
import requests 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.QtGui import QPalette, QColor, QPixmap, QImage
from PyQt5.QtWidgets import QApplication, QStyleFactory from PyQt5.QtWidgets import QApplication, QStyleFactory
from requests.exceptions import HTTPError from requests.exceptions import HTTPError
@ -25,6 +34,7 @@ if platform.system() == "Windows":
from win32com.client import Dispatch # pylint: disable=E0401 from win32com.client import Dispatch # pylint: disable=E0401
from rare import image_dir, shared, resources_path from rare import image_dir, shared, resources_path
# Mac not supported # Mac not supported
from legendary.core import LegendaryCore from legendary.core import LegendaryCore
@ -66,27 +76,46 @@ def download_image(game, force=False):
# to get picture updates # to get picture updates
if not os.path.isfile(f"{image_dir}/{game.app_name}/image.json"): 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: else:
json_data = json.load(open(f"{image_dir}/{game.app_name}/image.json", "r")) json_data = json.load(open(f"{image_dir}/{game.app_name}/image.json", "r"))
# Download # Download
for image in game.metadata["keyImages"]: 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(): if image["type"] not in json_data.keys():
json_data[image["type"]] = None json_data[image["type"]] = None
if json_data[image["type"]] != image["md5"] or not os.path.isfile( 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 # Download
json_data[image["type"]] = image["md5"] json_data[image["type"]] = image["md5"]
# os.remove(f"{image_dir}/{game.app_name}/{image['type']}.png") # 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}") logger.info(f"Download Image for Game: {game.app_title}")
url = image["url"] url = image["url"]
resp = requests.get(url) resp = requests.get(url)
img = QImage() img = QImage()
img.loadFromData(resp.content) img.loadFromData(resp.content)
img = img.scaled(200, 200 * 4 // 3, Qt.KeepAspectRatio, transformMode=Qt.SmoothTransformation) img = img.scaled(
img.save(os.path.join(image_dir, game.app_name, image["type"] + ".png"), format="PNG") 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 = { color_role_map = {
@ -145,9 +174,13 @@ def load_color_scheme(path: str) -> QPalette:
def set_color_pallete(color_scheme: str): def set_color_pallete(color_scheme: str):
if not color_scheme: 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().setStyleSheet("")
QApplication.instance().setPalette(QApplication.instance().style().standardPalette()) QApplication.instance().setPalette(
QApplication.instance().style().standardPalette()
)
return return
QApplication.instance().setStyle(QStyleFactory.create("Fusion")) QApplication.instance().setStyle(QStyleFactory.create("Fusion"))
custom_palette = load_color_scheme(f":/schemes/{color_scheme}") 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): def set_style_sheet(style_sheet: str):
if not style_sheet: 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("") QApplication.instance().setStyleSheet("")
return return
QApplication.instance().setStyle(QStyleFactory.create("Fusion")) QApplication.instance().setStyle(QStyleFactory.create("Fusion"))
@ -195,7 +230,9 @@ def get_translations():
def get_latest_version(): def get_latest_version():
try: 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"] tag = resp.json()["tag_name"]
return tag return tag
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
@ -203,7 +240,7 @@ def get_latest_version():
def get_size(b: int) -> str: 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: if b < 1024:
return f"{b:.2f}{i}B" return f"{b:.2f}{i}B"
b /= 1024 b /= 1024
@ -226,21 +263,22 @@ def create_rare_desktop_link(type_of_link):
else: else:
executable = f"{sys.executable} {os.path.abspath(sys.argv[0])}" executable = f"{sys.executable} {os.path.abspath(sys.argv[0])}"
with open(os.path.join(path, "Rare.desktop"), "w") as desktop_file: with open(os.path.join(path, "Rare.desktop"), "w") as desktop_file:
desktop_file.write("[Desktop Entry]\n" desktop_file.write(
f"Name=Rare\n" "[Desktop Entry]\n"
f"Type=Application\n" f"Name=Rare\n"
f"Icon={os.path.join(resources_path, 'images', 'Rare.png')}\n" f"Type=Application\n"
f"Exec={executable}\n" f"Icon={os.path.join(resources_path, 'images', 'Rare.png')}\n"
"Terminal=false\n" f"Exec={executable}\n"
"StartupWMClass=rare\n" "Terminal=false\n"
) "StartupWMClass=rare\n"
)
desktop_file.close() desktop_file.close()
os.chmod(os.path.expanduser(os.path.join(path, "Rare.desktop")), 0o755) os.chmod(os.path.expanduser(os.path.join(path, "Rare.desktop")), 0o755)
elif platform.system() == "Windows": elif platform.system() == "Windows":
# Target of shortcut # Target of shortcut
if type_of_link == "desktop": if type_of_link == "desktop":
target_folder = os.path.expanduser('~/Desktop/') target_folder = os.path.expanduser("~/Desktop/")
elif type_of_link == "start_menu": elif type_of_link == "start_menu":
target_folder = os.path.expandvars("%appdata%/Microsoft/Windows/Start Menu") target_folder = os.path.expandvars("%appdata%/Microsoft/Windows/Start Menu")
else: else:
@ -255,7 +293,7 @@ def create_rare_desktop_link(type_of_link):
executable = executable.replace("python.exe", "pythonw.exe") executable = executable.replace("python.exe", "pythonw.exe")
logger.debug(executable) logger.debug(executable)
# Add shortcut # Add shortcut
shell = Dispatch('WScript.Shell') shell = Dispatch("WScript.Shell")
shortcut = shell.CreateShortCut(pathLink) shortcut = shell.CreateShortCut(pathLink)
shortcut.Targetpath = executable shortcut.Targetpath = executable
if not sys.executable.endswith("Rare.exe"): 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: def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop") -> bool:
igame = core.get_installed_game(app_name) 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 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 icon = p
else: 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", "") icon = icon.replace(".png", "")
# Linux # Linux
@ -293,14 +335,15 @@ def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop") -
else: else:
executable = f"{sys.executable} {os.path.abspath(sys.argv[0])}" executable = f"{sys.executable} {os.path.abspath(sys.argv[0])}"
with open(f"{path}{igame.title}.desktop", "w") as desktop_file: with open(f"{path}{igame.title}.desktop", "w") as desktop_file:
desktop_file.write("[Desktop Entry]\n" desktop_file.write(
f"Name={igame.title}\n" "[Desktop Entry]\n"
f"Type=Application\n" f"Name={igame.title}\n"
f"Icon={icon}.png\n" f"Type=Application\n"
f"Exec={executable} launch {app_name}\n" f"Icon={icon}.png\n"
"Terminal=false\n" f"Exec={executable} launch {app_name}\n"
"StartupWMClass=rare-game\n" "Terminal=false\n"
) "StartupWMClass=rare-game\n"
)
desktop_file.close() desktop_file.close()
os.chmod(os.path.expanduser(f"{path}{igame.title}.desktop"), 0o755) 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": elif platform.system() == "Windows":
# Target of shortcut # Target of shortcut
if type_of_link == "desktop": if type_of_link == "desktop":
target_folder = os.path.expanduser('~/Desktop/') target_folder = os.path.expanduser("~/Desktop/")
elif type_of_link == "start_menu": elif type_of_link == "start_menu":
target_folder = os.path.expandvars("%appdata%/Microsoft/Windows/Start Menu") target_folder = os.path.expandvars("%appdata%/Microsoft/Windows/Start Menu")
else: else:
@ -322,20 +365,20 @@ def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop") -
for c in r'<>?":|\/*': for c in r'<>?":|\/*':
linkName.replace(c, "") linkName.replace(c, "")
linkName = linkName.strip() + '.lnk' linkName = linkName.strip() + ".lnk"
# Path to location of link file # Path to location of link file
pathLink = os.path.join(target_folder, linkName) pathLink = os.path.join(target_folder, linkName)
# Add shortcut # Add shortcut
shell = Dispatch('WScript.Shell') shell = Dispatch("WScript.Shell")
shortcut = shell.CreateShortCut(pathLink) shortcut = shell.CreateShortCut(pathLink)
if sys.executable.endswith("Rare.exe"): if sys.executable.endswith("Rare.exe"):
executable = sys.executable executable = sys.executable
else: else:
executable = f"{sys.executable} {os.path.abspath(sys.argv[0])}" executable = f"{sys.executable} {os.path.abspath(sys.argv[0])}"
shortcut.Targetpath = executable shortcut.Targetpath = executable
shortcut.Arguments = f'launch {app_name}' shortcut.Arguments = f"launch {app_name}"
shortcut.WorkingDirectory = os.getcwd() shortcut.WorkingDirectory = os.getcwd()
# Icon # Icon
@ -383,9 +426,7 @@ def optimal_text_background(image: list) -> Tuple[int, int, int]:
return tuple(inverted) return tuple(inverted)
def text_color_for_background(background: Tuple[int, int, int]) -> Tuple[int, def text_color_for_background(background: Tuple[int, int, int]) -> Tuple[int, int, int]:
int,
int]:
""" """
Calculates whether a black or white text color would fit better for the 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 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 # see https://alienryderflex.com/hsp.html
(red, green, blue) = background (red, green, blue) = background
luminance = math.sqrt( luminance = math.sqrt(0.299 * red ** 2 + 0.587 * green ** 2 + 0.114 * blue ** 2)
0.299 * red ** 2 +
0.587 * green ** 2 +
0.114 * blue ** 2)
if luminance < 127: if luminance < 127:
return 255, 255, 255 return 255, 255, 255
else: else:
@ -408,45 +446,62 @@ class WineResolverSignals(QObject):
class WineResolver(QRunnable): class WineResolver(QRunnable):
def __init__(self, path: str, app_name: str, core: LegendaryCore): def __init__(self, path: str, app_name: str, core: LegendaryCore):
super(WineResolver, self).__init__() super(WineResolver, self).__init__()
self.setAutoDelete(True) self.setAutoDelete(True)
self.signals = WineResolverSignals() self.signals = WineResolverSignals()
self.wine_env = os.environ.copy() self.wine_env = os.environ.copy()
self.wine_env.update(core.get_app_environment(app_name)) self.wine_env.update(core.get_app_environment(app_name))
self.wine_env['WINEDLLOVERRIDES'] = 'winemenubuilder=d;mscoree=d;mshtml=d;' self.wine_env["WINEDLLOVERRIDES"] = "winemenubuilder=d;mscoree=d;mshtml=d;"
self.wine_env['DISPLAY'] = '' self.wine_env["DISPLAY"] = ""
self.wine_binary = core.lgd.config.get( self.wine_binary = core.lgd.config.get(
app_name, 'wine_executable', app_name,
fallback=core.lgd.config.get('default', 'wine_executable', fallback='wine')) "wine_executable",
self.winepath_binary = os.path.join(os.path.dirname(self.wine_binary), 'winepath') 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) self.path = PathSpec(core, app_name).cook(path)
@pyqtSlot() @pyqtSlot()
def run(self): 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 # pylint: disable=E1136
self.signals.result_ready[str].emit(str()) self.signals.result_ready[str].emit(str())
return 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 # pylint: disable=E1136
self.signals.result_ready[str].emit(str()) self.signals.result_ready[str].emit(str())
return return
path = self.path.strip().replace('/', '\\') path = self.path.strip().replace("/", "\\")
# lk: if path does not exist form # 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 # lk: if path exists and needs a case sensitive interpretation form
# cmd = [self.wine_binary, 'cmd', '/c', f'cd {path} & cd'] # cmd = [self.wine_binary, 'cmd', '/c', f'cd {path} & cd']
proc = subprocess.Popen(cmd, proc = subprocess.Popen(
stdout=subprocess.PIPE, stderr=subprocess.PIPE, cmd,
env=self.wine_env, shell=False, text=True) stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=self.wine_env,
shell=False,
text=True,
)
out, err = proc.communicate() out, err = proc.communicate()
# Clean wine output # Clean wine output
out = out.strip().strip('"') out = out.strip().strip('"')
proc = subprocess.Popen([self.winepath_binary, '-u', out], proc = subprocess.Popen(
stdout=subprocess.PIPE, stderr=subprocess.PIPE, [self.winepath_binary, "-u", out],
env=self.wine_env, shell=False, text=True) stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=self.wine_env,
shell=False,
text=True,
)
out, err = proc.communicate() out, err = proc.communicate()
real_path = os.path.realpath(out.strip()) real_path = os.path.realpath(out.strip())
# pylint: disable=E1136 # pylint: disable=E1136
@ -474,7 +529,11 @@ class CloudWorker(QRunnable):
def get_raw_save_path(game: Game): def get_raw_save_path(game: Game):
if game.supports_cloud_saves: 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): def get_default_platform(app_name):