Rare: pass through Black formatter
This commit is contained in:
parent
0d2f36e028
commit
8f89eb6e88
|
@ -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()))
|
||||||
|
|
||||||
|
|
88
rare/app.py
88
rare/app.py
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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 []
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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."))
|
||||||
|
|
|
@ -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']}")
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"):
|
||||||
|
|
|
@ -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})")
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)))
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
'''
|
"""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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]:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in a new issue