1
0
Fork 0
mirror of synced 2024-06-26 18:20:50 +12:00

Merge pull request #26 from Dummerle/dev

Added many features
This commit is contained in:
Dummerle 2021-04-07 21:09:52 +02:00 committed by GitHub
commit 85b9fac065
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1966 additions and 612 deletions

View file

@ -2,8 +2,6 @@
## What you can do ## What you can do
To Contribute first fork the repository
### Add translations ### Add translations
1. Execute ```pylupdate5 $(find -name "*.py") -ts Rare/languages/{your lang (two letters)}.ts``` in project directrory 1. Execute ```pylupdate5 $(find -name "*.py") -ts Rare/languages/{your lang (two letters)}.ts``` in project directrory
@ -21,4 +19,7 @@ exmples:
Select one Card of the project and implement it or make other changes Select one Card of the project and implement it or make other changes
If you made your changes, create a pull request ##Git crash-course
To contribute fork the repository and clone **your** repo. Then make your changes, add it to git with `git add .` and upload it to Github with `git commit -m "message"` and `git push` or with your IDE.
If you uploaded your changes, create a pull request

View file

@ -69,6 +69,8 @@ There are more options to contribute.
- If you are a designer, you can add Stylesheets or create a logo or a banner - If you are a designer, you can add Stylesheets or create a logo or a banner
- You can translate the application - You can translate the application
**Note:** Pull Requests please to "dev" branch
More Information is in CONTRIBUTING.md More Information is in CONTRIBUTING.md
## Images ## Images

View file

@ -88,12 +88,15 @@ class ImportWidget(QWidget):
if os.name != "nt": if os.name != "nt":
self.core.egl.appdata_path = os.path.join(self.data_path, self.core.egl.appdata_path = os.path.join(self.data_path,
f"drive_c/users/{getuser()}/Local Settings/Application Data/EpicGamesLauncher/Saved/Config/Windows") f"drive_c/users/{getuser()}/Local Settings/Application Data/EpicGamesLauncher/Saved/Config/Windows")
try:
if self.core.auth_import():
logger.info(f"Logged in as {self.core.lgd.userdata['displayName']}")
self.success.emit()
else:
logger.warning("Failed to import existing session")
except Exception as e:
logger.warning(e)
if self.core.auth_import():
logger.info(f"Logged in as {self.core.lgd.userdata['displayName']}")
self.success.emit()
else:
print("Lol")
logger.warning("Error: No valid session found") logger.warning("Error: No valid session found")
self.login_text.setText(self.tr("Error: No valid session found")) self.login_text.setText(self.tr("Error: No valid session found"))
self.import_button.setText(self.tr("Import")) self.import_button.setText(self.tr("Import"))

View file

@ -0,0 +1,44 @@
from PyQt5.QtWidgets import QDialog, QLabel, QVBoxLayout, QCheckBox, QFormLayout, QHBoxLayout, QPushButton
from qtawesome import icon
from custom_legendary.models.game import Game
class UninstallDialog(QDialog):
def __init__(self, game: Game):
super(UninstallDialog, self).__init__()
self.setWindowTitle("Uninstall Game")
self.info = 0
self.layout = QVBoxLayout()
self.info_text = QLabel(self.tr("Do you really want to uninstall {}").format(game.app_title))
self.layout.addWidget(self.info_text)
self.keep_files = QCheckBox(self.tr("Keep Files"))
self.form = QFormLayout()
self.form.setContentsMargins(0, 10, 0, 10)
self.form.addRow(QLabel(self.tr("Do you want to keep files?")), self.keep_files)
self.layout.addLayout(self.form)
self.button_layout = QHBoxLayout()
self.ok_button = QPushButton(icon("ei.remove-circle", color="red"), self.tr("Uninstall"))
self.ok_button.clicked.connect(self.ok)
self.cancel_button = QPushButton(self.tr("Cancel"))
self.cancel_button.clicked.connect(self.cancel)
self.button_layout.addStretch(1)
self.button_layout.addWidget(self.ok_button)
self.button_layout.addWidget(self.cancel_button)
self.layout.addLayout(self.button_layout)
self.setLayout(self.layout)
def get_information(self):
self.exec_()
return self.info
def ok(self):
self.info = {"keep_files": self.keep_files.isChecked()}
self.close()
def cancel(self):
self.info = 0
self.close()

View file

@ -1,4 +1,6 @@
from PyQt5.QtWidgets import QMainWindow from PyQt5.QtCore import QSettings
from PyQt5.QtGui import QCloseEvent
from PyQt5.QtWidgets import QMainWindow, QMessageBox
from Rare.Components.TabWidget import TabWidget from Rare.Components.TabWidget import TabWidget
@ -8,5 +10,15 @@ class MainWindow(QMainWindow):
super(MainWindow, self).__init__() super(MainWindow, self).__init__()
self.setGeometry(0, 0, 1200, 800) self.setGeometry(0, 0, 1200, 800)
self.setWindowTitle("Rare - GUI for legendary") self.setWindowTitle("Rare - GUI for legendary")
self.setCentralWidget(TabWidget(core)) self.tab_widget = TabWidget(core)
self.setCentralWidget(self.tab_widget)
self.show() self.show()
def closeEvent(self, e: QCloseEvent):
if QSettings().value("sys_tray", True, bool):
self.hide()
e.ignore()
elif self.tab_widget.downloadTab.active_game is not None and QMessageBox.question(self, "Close", self.tr("There is a download active. Do you really want to exit app?"), QMessageBox.Yes, QMessageBox.No) == QMessageBox.Yes:
e.accept()
else:
e.accept()

View file

@ -3,10 +3,10 @@ from PyQt5.QtWidgets import QTabWidget, QWidget
from qtawesome import icon from qtawesome import icon
from Rare.Components.TabUtils import TabBar, TabButtonWidget from Rare.Components.TabUtils import TabBar, TabButtonWidget
from Rare.Components.Tabs.CloudSaves.CloudSaves import SyncSaves from Rare.Components.Tabs.CloudSaves import SyncSaves
from Rare.Components.Tabs.Downloads.DownloadTab import DownloadTab from Rare.Components.Tabs.Downloads.DownloadTab import DownloadTab
from Rare.Components.Tabs.Games.GamesTab import GameTab from Rare.Components.Tabs.Games import GameTab
from Rare.Components.Tabs.Settings.SettingsTab import SettingsTab from Rare.Components.Tabs.Settings import SettingsTab
from Rare.utils.Models import InstallOptions from Rare.utils.Models import InstallOptions
from custom_legendary.core import LegendaryCore from custom_legendary.core import LegendaryCore
@ -23,7 +23,7 @@ class TabWidget(QTabWidget):
self.addTab(self.game_list, self.tr("Games")) self.addTab(self.game_list, self.tr("Games"))
self.downloadTab = DownloadTab(core, updates) self.downloadTab = DownloadTab(core, updates)
self.addTab(self.downloadTab, "Downloads" + (" (" + str(len(updates)) + ")" if len(updates) != 0 else "")) self.addTab(self.downloadTab, "Downloads" + (" (" + str(len(updates)) + ")" if len(updates) != 0 else ""))
self.downloadTab.finished.connect(self.game_list.default_widget.game_list.update_list) self.downloadTab.finished.connect(self.dl_finished)
self.game_list.default_widget.game_list.install_game.connect(lambda x: self.downloadTab.install_game(x)) self.game_list.default_widget.game_list.install_game.connect(lambda x: self.downloadTab.install_game(x))
self.game_list.game_info.info.verify_game.connect(lambda app_name: self.downloadTab.install_game( self.game_list.game_info.info.verify_game.connect(lambda app_name: self.downloadTab.install_game(
@ -31,7 +31,6 @@ class TabWidget(QTabWidget):
self.tabBarClicked.connect(lambda x: self.game_list.layout.setCurrentIndex(0) if x == 0 else None) self.tabBarClicked.connect(lambda x: self.game_list.layout.setCurrentIndex(0) if x == 0 else None)
# Commented, because it is not finished
self.cloud_saves = SyncSaves(core) self.cloud_saves = SyncSaves(core)
self.addTab(self.cloud_saves, "Cloud Saves") self.addTab(self.cloud_saves, "Cloud Saves")
@ -43,10 +42,15 @@ class TabWidget(QTabWidget):
self.addTab(self.account, "") self.addTab(self.account, "")
self.setTabEnabled(disabled_tab + 1, False) self.setTabEnabled(disabled_tab + 1, False)
# self.settings = SettingsTab(core) # self.settings = SettingsTab(core)
self.addTab(self.settings, icon("fa.gear", color='white'), None)
self.addTab(self.settings, icon("fa.gear", color='white'), "(!)" if self.settings.about.update_available else "")
self.setIconSize(QSize(25, 25)) self.setIconSize(QSize(25, 25))
self.tabBar().setTabButton(3, self.tabBar().RightSide, TabButtonWidget(core)) self.tabBar().setTabButton(3, self.tabBar().RightSide, TabButtonWidget(core))
def dl_finished(self):
self.game_list.default_widget.game_list.update_list()
self.setTabText(1, "Downloads")
def resizeEvent(self, event): def resizeEvent(self, event):
self.tabBar().setMinimumWidth(self.width()) self.tabBar().setMinimumWidth(self.width())
super(TabWidget, self).resizeEvent(event) super(TabWidget, self).resizeEvent(event)

View file

@ -12,15 +12,16 @@ class MiniWidget(QWidget):
self.layout = QVBoxLayout() self.layout = QVBoxLayout()
self.core = core self.core = core
self.layout.addWidget(QLabel("Account")) self.layout.addWidget(QLabel("Account"))
username = str(self.core.lgd.userdata.get("display_name")) username = self.core.lgd.userdata.get("display_name")
if not username: if not username:
self.core.login() self.core.login()
username = str(self.core.lgd.userdata.get("display_name")) username = self.core.lgd.userdata.get("display_name")
self.layout.addWidget(QLabel(self.tr("Logged in as ") + username)) self.layout.addWidget(QLabel(self.tr("Logged in as ") + str(username)))
self.open_browser = QPushButton(self.tr("Account settings")) self.open_browser = QPushButton(self.tr("Account settings"))
self.open_browser.clicked.connect(self.open_account) self.open_browser.clicked.connect(
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"))
@ -35,7 +36,5 @@ class MiniWidget(QWidget):
if reply == QMessageBox.Yes: if reply == QMessageBox.Yes:
self.core.lgd.invalidate_userdata() self.core.lgd.invalidate_userdata()
QCoreApplication.exit() # restart app
QCoreApplication.instance().exit_action(-133742) # restart exit code
def open_account(self):
webbrowser.open("https://www.epicgames.com/account/personal?productName=epicgames")

View file

@ -1,123 +0,0 @@
from logging import getLogger
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtWidgets import *
from Rare.Components.Dialogs.PathInputDialog import PathInputDialog
from Rare.Components.Tabs.CloudSaves.SyncWidget import SyncWidget
from Rare.utils.QtExtensions import WaitingSpinner
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import SaveGameStatus
logger = getLogger("Sync Saves")
class LoadThread(QThread):
signal = pyqtSignal(list)
def __init__(self, core: LegendaryCore):
super(LoadThread, self).__init__()
self.core = core
def run(self) -> None:
saves = self.core.get_save_games()
self.signal.emit(saves)
class SyncSaves(QScrollArea):
def __init__(self, core: LegendaryCore):
super(SyncSaves, self).__init__()
self.core = core
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.load_saves()
def load_saves(self):
self.widget = QWidget()
layout = QVBoxLayout()
layout.addWidget(WaitingSpinner())
layout.addWidget(QLabel("<h4>Loading Cloud Saves</h4>"))
layout.addStretch()
self.widget.setLayout(layout)
self.setWidget(self.widget)
self.start_thread = LoadThread(self.core)
self.start_thread.signal.connect(self.setup_ui)
self.start_thread.start()
self.igames = self.core.get_installed_list()
def setup_ui(self, saves: list):
self.start_thread.disconnect()
self.main_layout = QVBoxLayout()
self.title = QLabel(
f"<h1>" + self.tr("Cloud Saves") + "</h1>\n" + self.tr("Found Saves for folowing Games"))
self.main_layout.addWidget(self.title)
saves_games = []
for i in saves:
if not i.app_name in saves_games and self.core.is_installed(i.app_name):
saves_games.append(i.app_name)
if len(saves_games) == 0:
# QMessageBox.information(self.tr("No Games Found"), self.tr("Your games don't support cloud save"))
self.title.setText(
f"<h1>" + self.tr("Cloud Saves") + "</h1>\n" + self.tr("Your games does not support Cloud Saves"))
self.setWidget(self.title)
return
self.sync_all_button = QPushButton(self.tr("Sync all games"))
self.sync_all_button.clicked.connect(self.sync_all)
self.main_layout.addWidget(self.sync_all_button)
latest_save = {}
for i in sorted(saves, key=lambda a: a.datetime):
latest_save[i.app_name] = i
logger.info(f'Got {len(latest_save)} remote save game(s)')
self.widgets = []
for igame in self.igames:
game = self.core.get_game(igame.app_name)
if not game.supports_cloud_saves:
continue
if latest_save.get(igame.app_name):
sync_widget = SyncWidget(igame, latest_save[igame.app_name], self.core)
else:
continue
sync_widget.reload.connect(self.reload)
self.main_layout.addWidget(sync_widget)
self.widgets.append(sync_widget)
self.widget = QWidget()
self.widget.setLayout(self.main_layout)
self.setWidget(self.widget)
def reload(self):
self.setWidget(QWidget())
self.load_saves()
self.update()
def sync_all(self):
logger.info("Sync all Games")
for w in self.widgets:
if not w.igame.save_path:
save_path = self.core.get_save_path(w.igame.app_name)
if '%' in save_path or '{' in save_path:
self.logger.info_label("Could not find save_path")
save_path = PathInputDialog(self.tr("Found no savepath"),
self.tr("No save path was found. Please select path or skip"))
if save_path == "":
continue
else:
w.igame.save_path = save_path
if w.res == SaveGameStatus.SAME_AGE:
continue
if w.res == SaveGameStatus.REMOTE_NEWER:
logger.info("Download")
w.download()
elif w.res == SaveGameStatus.LOCAL_NEWER:
logger.info("Upload")
w.upload()

View file

@ -1,8 +1,8 @@
import os import os
from logging import getLogger from logging import getLogger
from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QHBoxLayout, QLabel from PyQt5.QtWidgets import QVBoxLayout, QPushButton, QHBoxLayout, QLabel, QGroupBox
from Rare.Components.Dialogs.PathInputDialog import PathInputDialog from Rare.Components.Dialogs.PathInputDialog import PathInputDialog
from custom_legendary.core import LegendaryCore from custom_legendary.core import LegendaryCore
@ -37,12 +37,14 @@ class _DownloadThread(QThread):
self.core.download_saves(self.app_name, self.latest_save.manifest_name, self.save_path, clean_dir=True) self.core.download_saves(self.app_name, self.latest_save.manifest_name, self.save_path, clean_dir=True)
class SyncWidget(QWidget): class SyncWidget(QGroupBox):
reload = pyqtSignal() reload = pyqtSignal()
def __init__(self, igame: InstalledGame, save, core: LegendaryCore): def __init__(self, igame: InstalledGame, save, core: LegendaryCore):
super(SyncWidget, self).__init__() super(SyncWidget, self).__init__(igame.title)
self.setObjectName("group")
self.layout = QVBoxLayout() self.layout = QVBoxLayout()
self.setContentsMargins(10, 20, 10, 20)
self.thr = None self.thr = None
self.core = core self.core = core
self.save = save self.save = save
@ -70,7 +72,7 @@ class SyncWidget(QWidget):
self.logger.info('No cloud or local savegame found.') self.logger.info('No cloud or local savegame found.')
return return
game_title = QLabel(f"<h2>{igame.title}</h2>") # game_title = QLabel(f"<h2>{igame.title}</h2>")
if self.dt_local: if self.dt_local:
local_save_date = QLabel( local_save_date = QLabel(
@ -124,14 +126,16 @@ class SyncWidget(QWidget):
self.upload_button.clicked.connect(self.upload) self.upload_button.clicked.connect(self.upload)
self.download_button.clicked.connect(self.download) self.download_button.clicked.connect(self.download)
self.info_text = QLabel(status) self.info_text = QLabel(status)
self.layout.addWidget(game_title) # self.layout.addWidget(game_title)
self.layout.addWidget(local_save_date) self.layout.addWidget(local_save_date)
self.layout.addWidget(cloud_save_date) self.layout.addWidget(cloud_save_date)
save_path_layout = QHBoxLayout() save_path_layout = QHBoxLayout()
self.save_path_text = QLabel(igame.save_path) self.save_path_text = QLabel(igame.save_path)
self.save_path_text.setTextInteractionFlags(Qt.TextSelectableByMouse)
self.save_path_text.setWordWrap(True) self.save_path_text.setWordWrap(True)
self.change_save_path = QPushButton(self.tr("Change path")) self.change_save_path = QPushButton(self.tr("Change path"))
self.change_save_path.setFixedWidth(100)
self.change_save_path.clicked.connect(self.change_path) self.change_save_path.clicked.connect(self.change_path)
save_path_layout.addWidget(self.save_path_text) save_path_layout.addWidget(self.save_path_text)
save_path_layout.addWidget(self.change_save_path) save_path_layout.addWidget(self.change_save_path)
@ -141,7 +145,7 @@ class SyncWidget(QWidget):
button_layout.addWidget(self.upload_button) button_layout.addWidget(self.upload_button)
button_layout.addWidget(self.download_button) button_layout.addWidget(self.download_button)
self.layout.addLayout(button_layout) self.layout.addLayout(button_layout)
self.layout.addStretch(1)
self.setLayout(self.layout) self.setLayout(self.layout)
def change_path(self): def change_path(self):

View file

@ -0,0 +1,121 @@
from logging import getLogger
from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtWidgets import *
from Rare.Components.Dialogs.PathInputDialog import PathInputDialog
from Rare.Components.Tabs.CloudSaves.SyncWidget import SyncWidget
from Rare.utils.QtExtensions import WaitingSpinner
from custom_legendary.core import LegendaryCore
from custom_legendary.models.game import SaveGameStatus
logger = getLogger("Sync Saves")
class LoadThread(QThread):
signal = pyqtSignal(list)
def __init__(self, core: LegendaryCore):
super(LoadThread, self).__init__()
self.core = core
def run(self) -> None:
saves = self.core.get_save_games()
self.signal.emit(saves)
class SyncSaves(QScrollArea):
def __init__(self, core: LegendaryCore):
super(SyncSaves, self).__init__()
self.core = core
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.load_saves()
def load_saves(self):
self.widget = QWidget()
layout = QVBoxLayout()
layout.addWidget(WaitingSpinner())
layout.addWidget(QLabel("<h4>Loading Cloud Saves</h4>"))
layout.addStretch()
self.widget.setLayout(layout)
self.setWidget(self.widget)
self.start_thread = LoadThread(self.core)
self.start_thread.signal.connect(self.setup_ui)
self.start_thread.start()
self.igames = self.core.get_installed_list()
def setup_ui(self, saves: list):
self.start_thread.disconnect()
self.main_layout = QVBoxLayout()
self.title = QLabel(
f"<h1>" + self.tr("Cloud Saves") + "</h1>\n" + self.tr("Found Saves for folowing Games"))
self.main_layout.addWidget(self.title)
saves_games = []
for i in saves:
if not i.app_name in saves_games and self.core.is_installed(i.app_name):
saves_games.append(i.app_name)
if len(saves_games) == 0:
# QMessageBox.information(self.tr("No Games Found"), self.tr("Your games don't support cloud save"))
self.title.setText(
f"<h1>" + self.tr("Cloud Saves") + "</h1>\n" + self.tr("Your games does not support Cloud Saves"))
self.setWidget(self.title)
return
self.sync_all_button = QPushButton(self.tr("Sync all games"))
self.sync_all_button.clicked.connect(self.sync_all)
self.main_layout.addWidget(self.sync_all_button)
latest_save = {}
for i in sorted(saves, key=lambda a: a.datetime):
latest_save[i.app_name] = i
logger.info(f'Got {len(latest_save)} remote save game(s)')
self.widgets = []
for igame in self.igames:
game = self.core.get_game(igame.app_name)
if not game.supports_cloud_saves:
continue
if latest_save.get(igame.app_name):
sync_widget = SyncWidget(igame, latest_save[igame.app_name], self.core)
else:
continue
sync_widget.reload.connect(self.reload)
self.main_layout.addWidget(sync_widget)
self.widgets.append(sync_widget)
self.widget = QWidget()
self.main_layout.addStretch(1)
self.widget.setLayout(self.main_layout)
self.setWidgetResizable(True)
self.setWidget(self.widget)
def reload(self):
self.setWidget(QWidget())
self.load_saves()
self.update()
def sync_all(self):
logger.info("Sync all Games")
for w in self.widgets:
if not w.igame.save_path:
save_path = self.core.get_save_path(w.igame.app_name)
if '%' in save_path or '{' in save_path:
self.logger.info_label("Could not find save_path")
save_path = PathInputDialog(self.tr("Found no savepath"),
self.tr("No save path was found. Please select path or skip"))
if save_path == "":
continue
else:
w.igame.save_path = save_path
if w.res == SaveGameStatus.SAME_AGE:
continue
if w.res == SaveGameStatus.REMOTE_NEWER:
logger.info("Download")
w.download()
elif w.res == SaveGameStatus.LOCAL_NEWER:
logger.info("Upload")
w.upload()

View file

@ -0,0 +1,131 @@
from logging import getLogger
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QGroupBox, QVBoxLayout, QLabel, QHBoxLayout, QPushButton, QWidget
from qtawesome import icon
logger = getLogger("QueueWidget")
class DlWidget(QWidget):
move_up = pyqtSignal(str) # app_name
move_down = pyqtSignal(str) # app_name
remove = pyqtSignal(str) # app_name
def __init__(self, index, item):
super(DlWidget, self).__init__()
self.app_name = item[1].app_name
self.layout = QHBoxLayout()
self.left_layout = QVBoxLayout()
self.move_up_button = QPushButton(icon("fa.arrow-up", color="white"), "")
if index == 0:
self.move_up_button.setDisabled(True)
self.move_up_button.clicked.connect(lambda: self.move_up.emit(self.app_name))
self.move_up_button.setFixedWidth(20)
self.left_layout.addWidget(self.move_up_button)
self.move_down_buttton = QPushButton(icon("fa.arrow-down", color="white"), "")
self.move_down_buttton.clicked.connect(lambda: self.move_down.emit(self.app_name))
self.left_layout.addWidget(self.move_down_buttton)
self.move_down_buttton.setFixedWidth(20)
self.layout.addLayout(self.left_layout)
self.right_layout = QVBoxLayout()
self.title = QLabel(item[1].app_title)
self.right_layout.addWidget(self.title)
dl_size = item[-1].dl_size
install_size = item[-1].install_size
self.size = QHBoxLayout()
self.size.addWidget(QLabel(self.tr("Download size: {} GB").format(dl_size / 1024 ** 3)))
self.size.addWidget(QLabel(self.tr("Install size: {} GB").format(install_size / 1024 ** 3)))
self.right_layout.addLayout(self.size)
self.delete = QPushButton(self.tr("Remove Download"))
self.delete.clicked.connect(lambda: self.remove.emit(self.app_name))
self.right_layout.addWidget(self.delete)
self.layout.addLayout(self.right_layout)
self.setLayout(self.layout)
class DlQueueWidget(QGroupBox):
update_list = pyqtSignal(list)
dl_queue = []
def __init__(self):
super(DlQueueWidget, self).__init__()
self.setTitle(self.tr("Download Queue"))
self.layout = QVBoxLayout()
self.setObjectName("group")
self.text = QLabel(self.tr("No downloads in queue"))
self.layout.addWidget(self.text)
self.setLayout(self.layout)
def update_queue(self, dl_queue: list):
logger.info("Update Queue " + ", ".join(i[1].app_title for i in dl_queue))
self.dl_queue = dl_queue
self.setLayout(QVBoxLayout())
QWidget().setLayout(self.layout)
self.layout = QVBoxLayout()
if len(dl_queue) == 0:
self.layout.addWidget(QLabel(self.tr("No downloads in queue")))
self.setLayout(self.layout)
return
for index, item in enumerate(dl_queue):
widget = DlWidget(index, item)
widget.remove.connect(self.remove)
widget.move_up.connect(self.move_up)
widget.move_down.connect(self.move_down)
self.layout.addWidget(widget)
if index + 1 == len(dl_queue):
widget.move_down_buttton.setDisabled(True)
self.setLayout(self.layout)
def remove(self, app_name):
for index, i in enumerate(self.dl_queue):
if i[1].app_name == app_name:
self.dl_queue.pop(index)
break
else:
logger.warning("BUG! ", app_name)
return
self.update_list.emit(self.dl_queue)
self.update_queue(self.dl_queue)
def move_up(self, app_name):
index: int
for i, item in enumerate(self.dl_queue):
if item[1].app_name == app_name:
index = i
break
else:
logger.warning("Could not find appname" + app_name)
return
self.dl_queue.insert(index - 1, self.dl_queue.pop(index))
self.update_list.emit(self.dl_queue)
self.update_queue(self.dl_queue)
def move_down(self, app_name):
index: int
for i, item in enumerate(self.dl_queue):
if item[1].app_name == app_name:
index = i
break
else:
logger.warning("Could not find appname" + app_name)
return
self.dl_queue.insert(index + 1, self.dl_queue.pop(index))
self.update_list.emit(self.dl_queue)
self.update_queue(self.dl_queue)

View file

@ -1,19 +1,16 @@
import datetime import datetime
import os
import queue
import subprocess
import time
from logging import getLogger from logging import getLogger
from multiprocessing import Queue as MPQueue from multiprocessing import Queue as MPQueue
from PyQt5.QtCore import QThread, pyqtSignal, Qt from PyQt5.QtCore import QThread, pyqtSignal, Qt
from PyQt5.QtWidgets import QWidget, QMessageBox, QVBoxLayout, QLabel, QGridLayout, QProgressBar, QPushButton, QDialog, \ from PyQt5.QtWidgets import QWidget, QMessageBox, QVBoxLayout, QLabel, QGridLayout, QProgressBar, QPushButton, QDialog, \
QListWidget, QHBoxLayout QListWidget, QHBoxLayout, QGroupBox
from Rare.Components.Dialogs.InstallDialog import InstallInfoDialog, InstallDialog from Rare.Components.Dialogs.InstallDialog import InstallInfoDialog, InstallDialog
from Rare.utils.Models import InstallOptions, KillDownloadException from Rare.Components.Tabs.Downloads.DlQueueWidget import DlQueueWidget
from Rare.Components.Tabs.Downloads.DownloadThread import DownloadThread
from Rare.utils.Models import InstallOptions
from custom_legendary.core import LegendaryCore from custom_legendary.core import LegendaryCore
from custom_legendary.downloader.manager import DLManager
from custom_legendary.models.downloading import UIUpdate from custom_legendary.models.downloading import UIUpdate
from custom_legendary.models.game import Game, InstalledGame from custom_legendary.models.game import Game, InstalledGame
from custom_legendary.utils.selective_dl import games from custom_legendary.utils.selective_dl import games
@ -21,108 +18,10 @@ from custom_legendary.utils.selective_dl import games
logger = getLogger("Download") logger = getLogger("Download")
class DownloadThread(QThread):
status = pyqtSignal(str)
statistics = pyqtSignal(UIUpdate)
kill = False
def __init__(self, dlm: DLManager, core: LegendaryCore, status_queue: MPQueue, igame, repair=False,
repair_file=None):
super(DownloadThread, self).__init__()
self.dlm = dlm
self.core = core
self.status_queue = status_queue
self.igame = igame
self.repair = repair
self.repair_file = repair_file
def run(self):
start_time = time.time()
try:
self.dlm.start()
time.sleep(1)
while self.dlm.is_alive():
if self.kill:
# raise KillDownloadException()
# TODO kill download queue, workers
pass
try:
self.statistics.emit(self.status_queue.get(timeout=1))
except queue.Empty:
pass
self.dlm.join()
except KillDownloadException:
self.status.emit("stop")
logger.info("Downlaod can be continued later")
self.dlm.kill()
return
except Exception as e:
logger.error(f"Installation failed after {time.time() - start_time:.02f} seconds: {e}")
self.status.emit("error")
return
else:
self.status.emit("dl_finished")
end_t = time.time()
game = self.core.get_game(self.igame.app_name)
postinstall = self.core.install_game(self.igame)
if postinstall:
self._handle_postinstall(postinstall, self.igame)
dlcs = self.core.get_dlc_for_game(self.igame.app_name)
if dlcs:
print('The following DLCs are available for this game:')
for dlc in dlcs:
print(f' - {dlc.app_title} (App name: {dlc.app_name}, version: {dlc.app_version})')
print('Manually installing DLCs works the same; just use the DLC app name instead.')
# install_dlcs = QMessageBox.question(self, "", "Do you want to install the prequisites", QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes
# TODO
if game.supports_cloud_saves and not game.is_dlc:
logger.info('This game supports cloud saves, syncing is handled by the "sync-saves" command.')
logger.info(f'To download saves for this game run "legendary sync-saves {game.app_name}"')
old_igame = self.core.get_installed_game(game.app_name)
if old_igame and self.repair and os.path.exists(self.repair_file):
if old_igame.needs_verification:
old_igame.needs_verification = False
self.core.install_game(old_igame)
logger.debug('Removing repair file.')
os.remove(self.repair_file)
if old_igame and old_igame.install_tags != self.igame.install_tags:
old_igame.install_tags = self.igame.install_tags
self.logger.info('Deleting now untagged files.')
self.core.uninstall_tag(old_igame)
self.core.install_game(old_igame)
self.status.emit("finish")
def _handle_postinstall(self, postinstall, igame):
print('This game lists the following prequisites to be installed:')
print(f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}')
if os.name == 'nt':
if QMessageBox.question(self, "", "Do you want to install the prequisites",
QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
self.core.prereq_installed(igame.app_name)
req_path, req_exec = os.path.split(postinstall['path'])
work_dir = os.path.join(igame.install_path, req_path)
fullpath = os.path.join(work_dir, req_exec)
subprocess.call([fullpath, postinstall['args']], cwd=work_dir)
else:
self.core.prereq_installed(self.igame.app_name)
else:
logger.info('Automatic installation not available on Linux.')
class DownloadTab(QWidget): class DownloadTab(QWidget):
finished = pyqtSignal() finished = pyqtSignal()
thread: QThread thread: QThread
dl_queue = []
def __init__(self, core: LegendaryCore, updates: list): def __init__(self, core: LegendaryCore, updates: list):
super(DownloadTab, self).__init__() super(DownloadTab, self).__init__()
@ -146,7 +45,6 @@ class DownloadTab(QWidget):
self.layout.addLayout(self.info_layout) self.layout.addLayout(self.info_layout)
self.mini_layout = QHBoxLayout() self.mini_layout = QHBoxLayout()
self.prog_bar = QProgressBar() self.prog_bar = QProgressBar()
self.prog_bar.setMaximum(100) self.prog_bar.setMaximum(100)
self.mini_layout.addWidget(self.prog_bar) self.mini_layout.addWidget(self.prog_bar)
@ -158,33 +56,39 @@ class DownloadTab(QWidget):
self.layout.addLayout(self.mini_layout) self.layout.addLayout(self.mini_layout)
self.update_title = QLabel(f"<h2>{self.tr('Updates')}</h2>") self.queue_widget = DlQueueWidget()
self.update_title.setStyleSheet(""" self.layout.addWidget(self.queue_widget)
QLabel{ self.queue_widget.update_list.connect(self.update_dl_queue)
margin-top: 20px;
}
""")
self.layout.addWidget(self.update_title)
self.update_widgets = {}
if not updates:
self.update_text = QLabel(self.tr("No updates available"))
self.layout.addWidget(self.update_text)
else:
for igame in updates:
widget = UpdateWidget(core, igame)
self.update_widgets[igame.app_name] = widget
self.layout.addWidget(widget)
widget.update.connect(self.update_game)
self.updates = QGroupBox(self.tr("Updates"))
self.updates.setObjectName("group")
self.update_layout = QVBoxLayout()
self.update_widgets = {}
self.update_text = QLabel(self.tr("No updates available"))
self.update_text.setVisible(len(updates) == 0)
self.update_layout.addWidget(self.update_text)
for igame in updates:
widget = UpdateWidget(core, igame)
self.update_widgets[igame.app_name] = widget
self.update_layout.addWidget(widget)
widget.update.connect(self.update_game)
self.updates.setLayout(self.update_layout)
self.layout.addWidget(self.updates)
self.layout.addStretch(1) self.layout.addStretch(1)
self.setLayout(self.layout) self.setLayout(self.layout)
def update_dl_queue(self, dl_queue):
self.dl_queue = dl_queue
def stop_download(self): def stop_download(self):
self.thread.kill = True self.thread.kill = True
def install_game(self, options: InstallOptions): def install_game(self, options: InstallOptions):
game = self.core.get_game(options.app_name, update_meta=True)
status_queue = MPQueue() status_queue = MPQueue()
try: try:
dlm, analysis, game, igame, repair, repair_file = self.core.prepare_download( dlm, analysis, game, igame, repair, repair_file = self.core.prepare_download(
@ -221,21 +125,27 @@ class DownloadTab(QWidget):
if not analysis.dl_size: if not analysis.dl_size:
QMessageBox.information(self, "Warning", self.tr("Download size is 0. Game already exists")) QMessageBox.information(self, "Warning", self.tr("Download size is 0. Game already exists"))
return return
# Information # Information
if not InstallInfoDialog(dl_size=analysis.dl_size, install_size=analysis.install_size).get_accept(): if not InstallInfoDialog(dl_size=analysis.dl_size, install_size=analysis.install_size).get_accept():
return return
if self.active_game is None:
self.start_installation(dlm, game, status_queue, igame, repair_file, options, analysis)
else:
self.dl_queue.append((dlm, game, status_queue, igame, repair_file, options, analysis))
self.queue_widget.update_queue(self.dl_queue)
def start_installation(self, dlm, game, status_queue, igame, repair_file, options: InstallOptions, analysis):
print("start installation", game.app_title)
self.active_game = game self.active_game = game
self.installing_game_widget.setText(self.tr("Installing game: ")+self.active_game.app_title)
self.thread = DownloadThread(dlm, self.core, status_queue, igame, options.repair, repair_file) self.thread = DownloadThread(dlm, self.core, status_queue, igame, options.repair, repair_file)
self.thread.status.connect(self.status) self.thread.status.connect(self.status)
self.thread.statistics.connect(self.statistics) self.thread.statistics.connect(self.statistics)
self.thread.start() self.thread.start()
self.kill_button.setDisabled(False) self.kill_button.setDisabled(False)
self.installing_game.setText("Installing Game: " + self.active_game.app_title) self.installing_game.setText(self.tr("Installing Game: ") + self.active_game.app_title)
for i in self.update_widgets.values():
i.update_button.setDisabled(True)
def sdl_prompt(self, app_name: str = '', title: str = '') -> list: def sdl_prompt(self, app_name: str = '', title: str = '') -> list:
sdl = QDialog() sdl = QDialog()
@ -279,6 +189,8 @@ class DownloadTab(QWidget):
if text == "dl_finished": if text == "dl_finished":
pass pass
elif text == "finish": elif text == "finish":
self.installing_game.setText(self.tr("Download finished. Reload library"))
try: try:
from notifypy import Notify from notifypy import Notify
except ModuleNotFoundError: except ModuleNotFoundError:
@ -290,30 +202,43 @@ class DownloadTab(QWidget):
notification.send() notification.send()
# QMessageBox.information(self, "Info", "Download finished") # QMessageBox.information(self, "Info", "Download finished")
logger.info("Download finished: " + self.active_game.app_title) logger.info("Download finished: " + self.active_game.app_title)
if self.dl_queue:
if self.dl_queue[0][1] == self.active_game.app_name:
self.dl_queue.pop(0)
self.queue_widget.update_queue(self.dl_queue)
if self.active_game.app_name in self.update_widgets.keys(): if self.active_game.app_name in self.update_widgets.keys():
self.update_widgets[self.active_game.app_name].setVisible(False) self.update_widgets[self.active_game.app_name].setVisible(False)
self.update_widgets.pop(self.active_game.app_name) self.update_widgets.pop(self.active_game.app_name)
self.active_game = None
for i in self.update_widgets.values(): for i in self.update_widgets.values():
i.update_button.setDisabled(False) i.update_button.setDisabled(False)
self.finished.emit() self.finished.emit()
self.installing_game.setText(self.tr("Installing Game: No active download")) self.reset_infos()
self.prog_bar.setValue(0)
if len(self.dl_queue) != 0:
self.start_installation(*self.dl_queue[0])
else:
self.queue_widget.update_queue(self.dl_queue)
self.dl_speed.setText("")
self.cache_used.setText("")
self.downloaded.setText("")
elif text == "error": elif text == "error":
QMessageBox.warning(self, "warn", "Download error") QMessageBox.warning(self, "warn", "Download error")
elif text == "stop": elif text == "stop":
self.kill_button.setDisabled(True) self.reset_infos()
self.installing_game.setText(self.tr("Installing Game: No active download"))
self.prog_bar.setValue(0) def reset_infos(self):
self.dl_speed.setText("") self.kill_button.setDisabled(True)
self.cache_used.setText("") self.installing_game.setText(self.tr("Installing Game: No active download"))
self.downloaded.setText("") self.prog_bar.setValue(0)
self.dl_speed.setText("")
self.time_left.setText("")
self.cache_used.setText("")
self.downloaded.setText("")
def statistics(self, ui_update: UIUpdate): def statistics(self, ui_update: UIUpdate):
self.prog_bar.setValue(ui_update.progress) self.prog_bar.setValue(ui_update.progress)
@ -326,23 +251,19 @@ class DownloadTab(QWidget):
return str(datetime.timedelta(seconds=seconds)) return str(datetime.timedelta(seconds=seconds))
def update_game(self, app_name: str): def update_game(self, app_name: str):
print("Update ", app_name) logger.info("Update " + app_name)
infos = InstallDialog(app_name, self.core, True).get_information() infos = InstallDialog(app_name, self.core, True).get_information()
if infos != 0: if infos != 0:
path, max_workers, force, ignore_free_space = infos path, max_workers, force, ignore_free_space = infos
self.install_game(InstallOptions(app_name=app_name, max_workers=max_workers, path=path, self.install_game(InstallOptions(app_name=app_name, max_workers=max_workers, path=path,
force=force, ignore_free_space=ignore_free_space)) force=force, ignore_free_space=ignore_free_space))
def repair(self):
pass
class UpdateWidget(QWidget): class UpdateWidget(QWidget):
update = pyqtSignal(str) update = pyqtSignal(str)
def __init__(self, core: LegendaryCore, game: InstalledGame): def __init__(self, core: LegendaryCore, game: InstalledGame):
super(UpdateWidget, self).__init__() super(UpdateWidget, self).__init__()
print(game)
self.core = core self.core = core
self.game = game self.game = game

View file

@ -0,0 +1,117 @@
import os
import queue
import subprocess
import time
from logging import getLogger
from multiprocessing import Queue as MPQueue
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import QMessageBox
from Rare.utils.Models import KillDownloadException
from custom_legendary.core import LegendaryCore
from custom_legendary.downloader.manager import DLManager
from custom_legendary.models.downloading import UIUpdate
logger = getLogger("Download")
class DownloadThread(QThread):
status = pyqtSignal(str)
statistics = pyqtSignal(UIUpdate)
kill = False
def __init__(self, dlm: DLManager, core: LegendaryCore, status_queue: MPQueue, igame, repair=False,
repair_file=None):
super(DownloadThread, self).__init__()
self.dlm = dlm
self.core = core
self.status_queue = status_queue
self.igame = igame
self.repair = repair
self.repair_file = repair_file
def run(self):
start_time = time.time()
try:
self.dlm.start()
time.sleep(1)
while self.dlm.is_alive():
if self.kill:
# raise KillDownloadException()
# TODO kill download queue, workers
pass
try:
self.statistics.emit(self.status_queue.get(timeout=1))
except queue.Empty:
pass
self.dlm.join()
except KillDownloadException:
self.status.emit("stop")
logger.info("Downlaod can be continued later")
self.dlm.kill()
return
except Exception as e:
logger.error(f"Installation failed after {time.time() - start_time:.02f} seconds: {e}")
self.status.emit("error")
return
else:
self.status.emit("dl_finished")
end_t = time.time()
game = self.core.get_game(self.igame.app_name)
postinstall = self.core.install_game(self.igame)
if postinstall:
self._handle_postinstall(postinstall, self.igame)
dlcs = self.core.get_dlc_for_game(self.igame.app_name)
if dlcs:
print('The following DLCs are available for this game:')
for dlc in dlcs:
print(f' - {dlc.app_title} (App name: {dlc.app_name}, version: {dlc.app_version})')
print('Manually installing DLCs works the same; just use the DLC app name instead.')
# install_dlcs = QMessageBox.question(self, "", "Do you want to install the prequisites", QMessageBox.Yes|QMessageBox.No) == QMessageBox.Yes
# TODO
if game.supports_cloud_saves and not game.is_dlc:
logger.info('This game supports cloud saves, syncing is handled by the "sync-saves" command.')
logger.info(f'To download saves for this game run "legendary sync-saves {game.app_name}"')
old_igame = self.core.get_installed_game(game.app_name)
if old_igame and self.repair and os.path.exists(self.repair_file):
if old_igame.needs_verification:
old_igame.needs_verification = False
self.core.install_game(old_igame)
logger.debug('Removing repair file.')
os.remove(self.repair_file)
if old_igame and old_igame.install_tags != self.igame.install_tags:
old_igame.install_tags = self.igame.install_tags
self.logger.info('Deleting now untagged files.')
self.core.uninstall_tag(old_igame)
self.core.install_game(old_igame)
self.status.emit("finish")
def _handle_postinstall(self, postinstall, igame):
print('This game lists the following prequisites to be installed:')
print(f'- {postinstall["name"]}: {" ".join((postinstall["path"], postinstall["args"]))}')
if os.name == 'nt':
if QMessageBox.question(self, "", "Do you want to install the prequisites",
QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
self.core.prereq_installed(igame.app_name)
req_path, req_exec = os.path.split(postinstall['path'])
work_dir = os.path.join(igame.install_path, req_path)
fullpath = os.path.join(work_dir, req_exec)
subprocess.call([fullpath, postinstall['args']], cwd=work_dir)
else:
self.core.prereq_installed(self.igame.app_name)
else:
logger.info('Automatic installation not available on Linux.')

View file

@ -6,6 +6,7 @@ from PyQt5.QtWidgets import QWidget, QPushButton, QVBoxLayout, QLabel, QHBoxLayo
QProgressBar, QStackedWidget, QGroupBox QProgressBar, QStackedWidget, QGroupBox
from qtawesome import icon from qtawesome import icon
from Rare.Components.Dialogs.UninstallDialog import UninstallDialog
from Rare.Components.Tabs.Games.GameInfo.GameSettings import GameSettings from Rare.Components.Tabs.Games.GameInfo.GameSettings import GameSettings
from Rare.utils import LegendaryApi from Rare.utils import LegendaryApi
from Rare.utils.LegendaryApi import VerifyThread from Rare.utils.LegendaryApi import VerifyThread
@ -65,6 +66,7 @@ class GameInfo(QWidget):
right_layout.addWidget(self.game_title) right_layout.addWidget(self.game_title)
self.dev = QLabel("Error") self.dev = QLabel("Error")
self.dev.setTextInteractionFlags(Qt.TextSelectableByMouse)
right_layout.addWidget(self.dev) right_layout.addWidget(self.dev)
self.app_name = QLabel("Error") self.app_name = QLabel("Error")
@ -72,12 +74,14 @@ class GameInfo(QWidget):
right_layout.addWidget(self.app_name) right_layout.addWidget(self.app_name)
self.version = QLabel("Error") self.version = QLabel("Error")
self.version.setTextInteractionFlags(Qt.TextSelectableByMouse)
right_layout.addWidget(self.version) right_layout.addWidget(self.version)
self.install_size = QLabel("Error") self.install_size = QLabel("Error")
right_layout.addWidget(self.install_size) right_layout.addWidget(self.install_size)
self.install_path = QLabel("Error") self.install_path = QLabel("Error")
self.install_path.setTextInteractionFlags(Qt.TextSelectableByMouse)
right_layout.addWidget(self.install_path) right_layout.addWidget(self.install_path)
top_layout.addLayout(right_layout) top_layout.addLayout(right_layout)
@ -95,10 +99,13 @@ class GameInfo(QWidget):
self.setLayout(self.layout) self.setLayout(self.layout)
def uninstall(self): def uninstall(self):
if QMessageBox.question(self, "Uninstall", self.tr("Are you sure to uninstall {}").format(self.game.app_title), infos = UninstallDialog(self.game).get_information()
QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes: if infos == 0:
LegendaryApi.uninstall(self.game.app_name, self.core) print("Cancel Uninstall")
self.update_list.emit() return
LegendaryApi.uninstall(self.game.app_name, self.core, infos)
self.update_list.emit()
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')

View file

@ -40,6 +40,7 @@ class GameList(QStackedWidget):
self.icon_scrollarea.setWidgetResizable(True) self.icon_scrollarea.setWidgetResizable(True)
self.icon_scrollarea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.icon_scrollarea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.list_scrollarea.setWidgetResizable(True)
self.list_scrollarea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) self.list_scrollarea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
self.info_text = self.tr("Installed Games: {} Available Games: {}").format( self.info_text = self.tr("Installed Games: {} Available Games: {}").format(
@ -53,7 +54,7 @@ class GameList(QStackedWidget):
self.list_layout = QVBoxLayout() self.list_layout = QVBoxLayout()
self.list_layout.addWidget(QLabel(self.info_text)) self.list_layout.addWidget(QLabel(self.info_text))
IMAGE_DIR = self.settings.value("img_dir", os.path.expanduser("~/.cache/rare"), str) IMAGE_DIR = self.settings.value("img_dir", os.path.expanduser("~/.cache/rare/images"), str)
self.updates = [] self.updates = []
self.widgets = {} self.widgets = {}
@ -80,7 +81,7 @@ class GameList(QStackedWidget):
icon_widget.show_info.connect(self.show_game_info.emit) icon_widget.show_info.connect(self.show_game_info.emit)
list_widget.show_info.connect(self.show_game_info.emit) list_widget.show_info.connect(self.show_game_info.emit)
icon_widget.launch_signal.connect(self.launch) icon_widget.finish_signal.connect(self.launch)
icon_widget.finish_signal.connect(self.finished) icon_widget.finish_signal.connect(self.finished)
list_widget.launch_signal.connect(self.launch) list_widget.launch_signal.connect(self.launch)
list_widget.finish_signal.connect(self.finished) list_widget.finish_signal.connect(self.finished)
@ -100,6 +101,7 @@ class GameList(QStackedWidget):
if not game.app_name in installed: if not game.app_name in installed:
uninstalled_games.append(game) uninstalled_games.append(game)
# add uninstalled games
for game in uninstalled_games: for game in uninstalled_games:
if os.path.exists(f"{IMAGE_DIR}/{game.app_name}/UninstalledArt.png"): if os.path.exists(f"{IMAGE_DIR}/{game.app_name}/UninstalledArt.png"):
pixmap = QPixmap(f"{IMAGE_DIR}/{game.app_name}/UninstalledArt.png") pixmap = QPixmap(f"{IMAGE_DIR}/{game.app_name}/UninstalledArt.png")
@ -110,15 +112,15 @@ class GameList(QStackedWidget):
pixmap = QPixmap(f"{IMAGE_DIR}/{game.app_name}/UninstalledArt.png") pixmap = QPixmap(f"{IMAGE_DIR}/{game.app_name}/UninstalledArt.png")
else: else:
logger.warning(f"No Image found: {self.game.app_title}") logger.warning(f"No Image found: {game.app_title}")
download_image(game, force=True) download_image(game, force=True)
pixmap = QPixmap(f"{IMAGE_DIR}/{game.app_name}/UninstalledArt.png") pixmap = QPixmap(f"{IMAGE_DIR}/{game.app_name}/UninstalledArt.png")
icon_widget = IconWidgetUninstalled(game, self.core, pixmap) icon_widget = IconWidgetUninstalled(game, self.core, pixmap)
icon_widget.install_game.connect(self.install_game.emit) icon_widget.install_game.connect(self.install)
list_widget = ListWidgetUninstalled(self.core, game, pixmap) list_widget = ListWidgetUninstalled(self.core, game, pixmap)
list_widget.install_game.connect(self.install_game.emit) list_widget.install_game.connect(self.install)
self.icon_layout.addWidget(icon_widget) self.icon_layout.addWidget(icon_widget)
self.list_layout.addWidget(list_widget) self.list_layout.addWidget(list_widget)
@ -143,6 +145,14 @@ class GameList(QStackedWidget):
if self.settings.value("installed_only", False, bool): if self.settings.value("installed_only", False, bool):
self.installed_only(True) self.installed_only(True)
def install(self, options: InstallOptions):
icon_widget, list_widget = self.widgets[options.app_name]
icon_widget.mousePressEvent = lambda e: None
icon_widget.installing = True
list_widget.install_button.setDisabled(True)
list_widget.installing = True
self.install_game.emit(options)
def finished(self, app_name): def finished(self, app_name):
self.widgets[app_name][0].info_text = "" self.widgets[app_name][0].info_text = ""
self.widgets[app_name][0].info_label.setText("") self.widgets[app_name][0].info_label.setText("")

View file

@ -25,6 +25,8 @@ class BaseInstalledWidget(QGroupBox):
self.game_running = False self.game_running = False
self.update_available = self.core.get_asset(self.game.app_name, True).build_version != igame.version self.update_available = self.core.get_asset(self.game.app_name, True).build_version != igame.version
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)
# self.setStyleSheet("border-radius: 5px") # self.setStyleSheet("border-radius: 5px")

View file

@ -17,6 +17,7 @@ class BaseUninstalledWidget(QGroupBox):
self.game = game self.game = game
self.core = core self.core = core
self.pixmap = pixmap self.pixmap = pixmap
self.installing = False
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)

View file

@ -37,7 +37,7 @@ class GameWidgetInstalled(BaseInstalledWidget):
if self.pixmap: if self.pixmap:
w = 200 w = 200
self.pixmap = self.pixmap.scaled(w, int(w * 4 / 3)) self.pixmap = self.pixmap.scaled(w, int(w * 4 / 3), transformMode=Qt.SmoothTransformation)
self.image = ClickableLabel() self.image = ClickableLabel()
self.image.setObjectName("game_widget") self.image.setObjectName("game_widget")
self.image.setPixmap(self.pixmap) self.image.setPixmap(self.pixmap)

View file

@ -1,7 +1,7 @@
import os import os
from logging import getLogger from logging import getLogger
from PyQt5.QtCore import QProcess, pyqtSignal from PyQt5.QtCore import QProcess, pyqtSignal, Qt
from PyQt5.QtWidgets import QHBoxLayout, QLabel, QPushButton, QStyle, QVBoxLayout from PyQt5.QtWidgets import QHBoxLayout, QLabel, QPushButton, QStyle, QVBoxLayout
from qtawesome import icon from qtawesome import icon
@ -30,7 +30,7 @@ class InstalledListWidget(BaseInstalledWidget):
self.childLayout = QVBoxLayout() self.childLayout = QVBoxLayout()
if self.pixmap: if self.pixmap:
self.pixmap = self.pixmap.scaled(180, 240) self.pixmap = self.pixmap.scaled(180, 240, transformMode=Qt.SmoothTransformation)
self.image = QLabel() self.image = QLabel()
self.image.setPixmap(self.pixmap) self.image.setPixmap(self.pixmap)
self.layout.addWidget(self.image) self.layout.addWidget(self.image)

View file

@ -18,7 +18,7 @@ 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()
self.setObjectName("game_widget_icon")
if self.pixmap: if self.pixmap:
w = 200 w = 200
self.pixmap = self.pixmap.scaled(w, int(w * 4 / 3)) self.pixmap = self.pixmap.scaled(w, int(w * 4 / 3))
@ -37,10 +37,17 @@ class IconWidgetUninstalled(BaseUninstalledWidget):
self.setFixedWidth(self.sizeHint().width()) self.setFixedWidth(self.sizeHint().width())
def mousePressEvent(self, e) -> None: def mousePressEvent(self, e) -> None:
self.install() if not self.installing:
self.install()
def enterEvent(self, e): def enterEvent(self, e):
self.info_label.setText(self.tr("Install Game")) if not self.installing:
self.info_label.setText(self.tr("Install Game"))
else:
self.info_label.setText(self.tr("Installation running"))
def leaveEvent(self, e): def leaveEvent(self, e):
self.info_label.setText("") if self.installing:
self.info_label.setText("Installation...")
else:
self.info_label.setText("")

View file

@ -1,107 +0,0 @@
from PyQt5.QtCore import QSettings, QSize
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QCheckBox, QLineEdit, QPushButton, QStackedLayout, QLabel
from qtawesome import icon
from Rare.Components.Tabs.Games.GameInfo.GameInfo import InfoTabs
from Rare.Components.Tabs.Games.GameList import GameList
from Rare.Components.Tabs.Games.ImportWidget import ImportWidget
from Rare.utils.QtExtensions import SelectViewWidget
class GameTab(QWidget):
def __init__(self, core):
super(GameTab, self).__init__()
self.layout = QStackedLayout()
self.default_widget = Games(core)
self.default_widget.game_list.show_game_info.connect(self.show_info)
self.default_widget.head_bar.import_game.clicked.connect(lambda: self.layout.setCurrentIndex(2))
self.layout.addWidget(self.default_widget)
self.game_info = InfoTabs(core)
self.game_info.info.update_list.connect(self.update_list)
self.layout.addWidget(self.game_info)
self.default_widget.head_bar.refresh_list.clicked.connect(self.update_list)
self.import_widget = ImportWidget(core)
self.layout.addWidget(self.import_widget)
self.import_widget.back_button.clicked.connect(lambda: self.layout.setCurrentIndex(0))
self.import_widget.update_list.connect(self.update_list)
self.setLayout(self.layout)
def update_list(self):
self.default_widget.game_list.update_list(not self.default_widget.head_bar.view.isChecked())
self.layout.setCurrentIndex(0)
def show_info(self, app_name):
self.game_info.update_game(app_name)
self.game_info.setCurrentIndex(1)
self.layout.setCurrentIndex(1)
class Games(QWidget):
def __init__(self, core):
super(Games, self).__init__()
self.layout = QVBoxLayout()
self.head_bar = GameListHeadBar()
self.head_bar.setObjectName("head_bar")
self.game_list = GameList(core)
self.head_bar.search_bar.textChanged.connect(
lambda: self.game_list.filter(self.head_bar.search_bar.text()))
self.head_bar.installed_only.stateChanged.connect(lambda:
self.game_list.installed_only(
self.head_bar.installed_only.isChecked()))
self.layout.addWidget(self.head_bar)
self.layout.addWidget(self.game_list)
# self.layout.addStretch(1)
self.head_bar.view.toggled.connect(self.toggle_view)
self.setLayout(self.layout)
def toggle_view(self):
self.game_list.setCurrentIndex(1 if self.head_bar.view.isChecked() else 0)
settings = QSettings()
settings.setValue("icon_view", not self.head_bar.view.isChecked())
class GameListHeadBar(QWidget):
def __init__(self):
super(GameListHeadBar, self).__init__()
self.layout = QHBoxLayout()
self.installed_only = QCheckBox(self.tr("Installed only"))
self.settings = QSettings()
self.installed_only.setChecked(self.settings.value("installed_only", False, bool))
self.layout.addWidget(self.installed_only)
self.layout.addStretch(1)
self.import_game = QPushButton(icon("mdi.import", color="white"), self.tr("Import Game"))
self.layout.addWidget(self.import_game)
self.layout.addStretch(1)
self.search_bar = QLineEdit()
self.search_bar.setObjectName("search_bar")
self.search_bar.setFrame(False)
icon_label = QLabel()
icon_label.setPixmap(icon("fa.search", color="white").pixmap(QSize(20, 20)))
self.layout.addWidget(icon_label)
self.search_bar.setMinimumWidth(200)
self.search_bar.setPlaceholderText(self.tr("Search Game"))
self.layout.addWidget(self.search_bar)
self.layout.addStretch(2)
checked = QSettings().value("icon_view", True, bool)
self.view = SelectViewWidget(checked)
self.layout.addWidget(self.view)
self.layout.addStretch(1)
self.refresh_list = QPushButton()
self.refresh_list.setIcon(icon("fa.refresh", color="white")) # Reload icon
self.layout.addWidget(self.refresh_list)
self.setLayout(self.layout)

View file

@ -4,7 +4,8 @@ import string
from logging import getLogger from logging import getLogger
from PyQt5.QtCore import pyqtSignal from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QWidget, QLabel, QHBoxLayout, QPushButton, QVBoxLayout, QFileDialog, QMessageBox, QLineEdit from PyQt5.QtWidgets import QWidget, QLabel, QHBoxLayout, QPushButton, QVBoxLayout, QFileDialog, QMessageBox, QLineEdit, \
QGroupBox
from qtawesome import icon from qtawesome import icon
from Rare.utils import LegendaryApi from Rare.utils import LegendaryApi
@ -34,11 +35,13 @@ class ImportWidget(QWidget):
self.title = QLabel("<h2>Import Game</h2") self.title = QLabel("<h2>Import Game</h2")
self.layout.addWidget(self.title) self.layout.addWidget(self.title)
self.import_one_game = QLabel(f"<h3>{self.tr('Import existing game from Epic Games Launcher')}</h3>") # self.import_one_game = QLabel(f"<h3>{self.tr('Import existing game from Epic Games Launcher')}</h3>")
self.layout.addWidget(self.import_one_game) self.import_one_game = QGroupBox(self.tr('Import existing game from Epic Games Launcher'))
self.import_one_game.setObjectName("group")
self.gb_layout = QVBoxLayout()
self.import_game_info = QLabel(self.tr("Select path to game")) self.import_game_info = QLabel(self.tr("Select path to game"))
self.layout.addWidget(self.import_game_info) self.gb_layout.addWidget(self.import_game_info)
self.override_app_name_label = QLabel(self.tr("Override app name (Only if imported game from legendary or the app could not find the app name)")) self.override_app_name_label = QLabel(self.tr("Override app name (Only if imported game from legendary or the app could not find the app name)"))
self.app_name_input = QLineEdit() self.app_name_input = QLineEdit()
@ -52,17 +55,21 @@ class ImportWidget(QWidget):
self.path_edit = PathEdit(os.path.expanduser("~"), QFileDialog.DirectoryOnly) self.path_edit = PathEdit(os.path.expanduser("~"), QFileDialog.DirectoryOnly)
self.path_edit.text_edit.textChanged.connect(self.path_changed) self.path_edit.text_edit.textChanged.connect(self.path_changed)
self.layout.addWidget(self.path_edit) self.gb_layout.addWidget(self.path_edit)
self.layout.addWidget(self.override_app_name_label) self.gb_layout.addWidget(self.override_app_name_label)
self.layout.addWidget(self.app_name_input) self.gb_layout.addWidget(self.app_name_input)
self.info_label = QLabel("") self.info_label = QLabel("")
self.layout.addWidget(self.info_label) self.gb_layout.addWidget(self.info_label)
self.import_button = QPushButton(self.tr("Import Game")) self.import_button = QPushButton(self.tr("Import Game"))
self.layout.addWidget(self.import_button) self.gb_layout.addWidget(self.import_button)
self.import_button.clicked.connect(self.import_game) self.import_button.clicked.connect(self.import_game)
self.import_one_game.setLayout(self.gb_layout)
self.layout.addWidget(self.import_one_game)
self.layout.addStretch(1) self.layout.addStretch(1)
self.auto_import = QLabel(f"<h3>{self.tr('Auto import all existing games')}</h3>") self.auto_import = QLabel(f"<h3>{self.tr('Auto import all existing games')}</h3>")
@ -104,6 +111,7 @@ class ImportWidget(QWidget):
if not path: if not path:
path = self.path_edit.text() path = self.path_edit.text()
if not app_name: if not app_name:
# try to find app name
if a_n := self.find_app_name(path): if a_n := self.find_app_name(path):
app_name = a_n app_name = a_n
else: else:
@ -134,7 +142,7 @@ class ImportWidget(QWidget):
continue continue
app_name = self.find_app_name(json_path) app_name = self.find_app_name(json_path)
if not app_name: if not app_name:
logger.warning("Could not find app name") logger.warning("Could not find app name at " + game_path)
continue continue
if LegendaryApi.import_game(self.core, app_name, game_path + path): if LegendaryApi.import_game(self.core, app_name, game_path + path):
@ -159,4 +167,4 @@ class ImportWidget(QWidget):
QMessageBox.information(self, "Imported Games", self.tr("Successfully imported {} Games. Reloading Library").format(imported)) QMessageBox.information(self, "Imported Games", self.tr("Successfully imported {} Games. Reloading Library").format(imported))
self.update_list.emit() self.update_list.emit()
else: else:
QMessageBox.information(self, "Imported Games", "No Games were found") QMessageBox.information(self, "Imported Games", self.tr("No Games were found"))

View file

@ -0,0 +1,107 @@
from PyQt5.QtCore import QSettings, QSize
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QCheckBox, QLineEdit, QPushButton, QStackedLayout, QLabel
from qtawesome import icon
from Rare.Components.Tabs.Games.GameInfo.GameInfo import InfoTabs
from Rare.Components.Tabs.Games.GameList import GameList
from Rare.Components.Tabs.Games.ImportWidget import ImportWidget
from Rare.utils.QtExtensions import SelectViewWidget
class GameTab(QWidget):
def __init__(self, core):
super(GameTab, self).__init__()
self.layout = QStackedLayout()
self.default_widget = Games(core)
self.default_widget.game_list.show_game_info.connect(self.show_info)
self.default_widget.head_bar.import_game.clicked.connect(lambda: self.layout.setCurrentIndex(2))
self.layout.addWidget(self.default_widget)
self.game_info = InfoTabs(core)
self.game_info.info.update_list.connect(self.update_list)
self.layout.addWidget(self.game_info)
self.default_widget.head_bar.refresh_list.clicked.connect(self.update_list)
self.import_widget = ImportWidget(core)
self.layout.addWidget(self.import_widget)
self.import_widget.back_button.clicked.connect(lambda: self.layout.setCurrentIndex(0))
self.import_widget.update_list.connect(self.update_list)
self.setLayout(self.layout)
def update_list(self):
self.default_widget.game_list.update_list(self.default_widget.head_bar.view.isChecked())
self.layout.setCurrentIndex(0)
def show_info(self, app_name):
self.game_info.update_game(app_name)
self.game_info.setCurrentIndex(1)
self.layout.setCurrentIndex(1)
class Games(QWidget):
def __init__(self, core):
super(Games, self).__init__()
self.layout = QVBoxLayout()
self.head_bar = GameListHeadBar()
self.head_bar.setObjectName("head_bar")
self.game_list = GameList(core)
self.head_bar.search_bar.textChanged.connect(
lambda: self.game_list.filter(self.head_bar.search_bar.text()))
self.head_bar.installed_only.stateChanged.connect(lambda:
self.game_list.installed_only(
self.head_bar.installed_only.isChecked()))
self.layout.addWidget(self.head_bar)
self.layout.addWidget(self.game_list)
# self.layout.addStretch(1)
self.head_bar.view.toggled.connect(self.toggle_view)
self.setLayout(self.layout)
def toggle_view(self):
self.game_list.setCurrentIndex(1 if self.head_bar.view.isChecked() else 0)
settings = QSettings()
settings.setValue("icon_view", not self.head_bar.view.isChecked())
class GameListHeadBar(QWidget):
def __init__(self):
super(GameListHeadBar, self).__init__()
self.layout = QHBoxLayout()
self.installed_only = QCheckBox(self.tr("Installed only"))
self.settings = QSettings()
self.installed_only.setChecked(self.settings.value("installed_only", False, bool))
self.layout.addWidget(self.installed_only)
self.layout.addStretch(1)
self.import_game = QPushButton(icon("mdi.import", color="white"), self.tr("Import Game"))
self.layout.addWidget(self.import_game)
self.layout.addStretch(1)
self.search_bar = QLineEdit()
self.search_bar.setObjectName("search_bar")
self.search_bar.setFrame(False)
icon_label = QLabel()
icon_label.setPixmap(icon("fa.search", color="white").pixmap(QSize(20, 20)))
self.layout.addWidget(icon_label)
self.search_bar.setMinimumWidth(200)
self.search_bar.setPlaceholderText(self.tr("Search Game"))
self.layout.addWidget(self.search_bar)
self.layout.addStretch(2)
checked = QSettings().value("icon_view", True, bool)
self.view = SelectViewWidget(checked)
self.layout.addWidget(self.view)
self.layout.addStretch(1)
self.refresh_list = QPushButton()
self.refresh_list.setIcon(icon("fa.refresh", color="white")) # Reload icon
self.layout.addWidget(self.refresh_list)
self.setLayout(self.layout)

View file

@ -1,6 +1,9 @@
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel import webbrowser
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton
from Rare import __version__ from Rare import __version__
from Rare.utils.utils import get_latest_version
class About(QWidget): class About(QWidget):
@ -13,6 +16,15 @@ class About(QWidget):
self.version = QLabel("Version: " + __version__) self.version = QLabel("Version: " + __version__)
self.layout.addWidget(self.version) self.layout.addWidget(self.version)
latest_tag = get_latest_version()
self.update_available = latest_tag != __version__
if latest_tag != __version__:
print(f"Update available: {__version__} -> {latest_tag}")
self.update_available = QLabel(self.tr("Update available: {} -> {}").format(__version__, latest_tag))
self.layout.addWidget(self.update_available)
self.open_browser = QPushButton(self.tr("Download latest release"))
self.layout.addWidget(self.open_browser)
self.open_browser.clicked.connect(lambda: webbrowser.open("https://github.com/Dummerle/Rare/releases/latest"))
self.dev = QLabel(self.tr("Developer:") + "<a href='https://github.com/Dummerle'>Dummerle</a>") self.dev = QLabel(self.tr("Developer:") + "<a href='https://github.com/Dummerle'>Dummerle</a>")
self.dev.setToolTip("Github") self.dev.setToolTip("Github")

View file

@ -41,8 +41,10 @@ class DxvkWidget(QGroupBox):
self.more_settings.setPopupMode(QToolButton.InstantPopup) self.more_settings.setPopupMode(QToolButton.InstantPopup)
self.more_settings.setMenu(QMenu()) self.more_settings.setMenu(QMenu())
self.more_settings.setText("More DXVK settings") self.more_settings.setText("More DXVK settings")
action = QWidgetAction(self) action = QWidgetAction(self)
self.more_settings_widget = DxvkMoreSettingsWidget(self.dxvk_settings, self.core) self.more_settings_widget = DxvkMoreSettingsWidget(self.dxvk_settings, self.core)
self.more_settings_widget.show_dxvk.connect(lambda x: self.show_dxvk.setChecked(x))
action.setDefaultWidget(self.more_settings_widget) action.setDefaultWidget(self.more_settings_widget)
self.more_settings.menu().addAction(action) self.more_settings.menu().addAction(action)
@ -71,7 +73,8 @@ class DxvkWidget(QGroupBox):
def update_dxvk_active(self): def update_dxvk_active(self):
if self.show_dxvk.isChecked(): if self.show_dxvk.isChecked():
if not f"{self.name}.env" in self.core.lgd.config.sections(): if not f"{self.name}.env" in self.core.lgd.config.sections():
self.core.lgd.config[f"{self.name}.env"] = {} print("add section dxvk")
self.core.lgd.config.add_section(f"{self.name}.env")
self.more_settings.setDisabled(False) self.more_settings.setDisabled(False)
for i in self.more_settings_widget.settings: for i in self.more_settings_widget.settings:
@ -93,11 +96,11 @@ class DxvkWidget(QGroupBox):
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")
print("Remove Section DXVK_HUD")
self.core.lgd.save_config() self.core.lgd.save_config()
class DxvkMoreSettingsWidget(QWidget): class DxvkMoreSettingsWidget(QWidget):
show_dxvk = pyqtSignal(bool)
def __init__(self, settings: dict, core: LegendaryCore): def __init__(self, settings: dict, core: LegendaryCore):
super(DxvkMoreSettingsWidget, self).__init__() super(DxvkMoreSettingsWidget, self).__init__()
self.layout = QVBoxLayout() self.layout = QVBoxLayout()
@ -118,16 +121,22 @@ class DxvkMoreSettingsWidget(QWidget):
y = list(self.settings[tag]) y = list(self.settings[tag])
y[0] = checked y[0] = checked
self.settings[tag] = tuple(y) self.settings[tag] = tuple(y)
# print(self.settings)
sett = [] sett = []
logger.debug(self.settings) logger.debug(self.settings)
for i in self.settings: for i in self.settings:
check, _ = self.settings[i] check, _ = self.settings[i]
if check: if check:
sett.append(i) sett.append(i)
if sett: if len(sett) != 0:
self.core.lgd.config[f"{self.name}.env"]["DXVK_HUD"] = ",".join(sett) self.core.lgd.config.set(f"{self.name}.env", "DXVK_HUD", ",".join(sett))
self.core.lgd.save_config()
else:
self.core.lgd.config.remove_option(f"{self.name}.env", "DXVK_HUD")
self.show_dxvk.emit(False)
if not self.core.lgd.config.options(f"{self.name}.env"):
self.core.lgd.config.remove_section(f"{self.name}.env")
self.core.lgd.save_config()
class CheckBox(QCheckBox): class CheckBox(QCheckBox):

View file

@ -10,14 +10,15 @@ from custom_legendary.core import LegendaryCore
logger = getLogger("LegendarySettings") logger = getLogger("LegendarySettings")
class LegendarySettings(QWidget): class LegendarySettings(QGroupBox):
def __init__(self, core: LegendaryCore): def __init__(self, core: LegendaryCore):
super(LegendarySettings, self).__init__() super(LegendarySettings, self).__init__()
self.setTitle(self.tr("Legendary settings"))
self.layout = QVBoxLayout() self.layout = QVBoxLayout()
self.core = core self.core = core
self.title = QLabel("<h2>" + self.tr("Legendary settings") + "</h2>") #self.title = QLabel("<h2>" + self.tr("Legendary settings") + "</h2>")
self.layout.addWidget(self.title) #self.layout.addWidget(self.title)
self.setObjectName("group")
# Default installation directory # Default installation directory
self.select_path = PathEdit(core.get_default_install_dir(), type_of_file=QFileDialog.DirectoryOnly, self.select_path = PathEdit(core.get_default_install_dir(), type_of_file=QFileDialog.DirectoryOnly,
infotext="Default") infotext="Default")
@ -39,8 +40,7 @@ class LegendarySettings(QWidget):
#cleanup #cleanup
self.clean_layout = QVBoxLayout() self.clean_layout = QVBoxLayout()
self.cleanup_widget = QGroupBox() self.cleanup_widget = QGroupBox(self.tr("Cleanup"))
self.cleanup_widget.setTitle(self.tr("Cleanup"))
self.clean_button = QPushButton(self.tr("Remove everything")) self.clean_button = QPushButton(self.tr("Remove everything"))
self.clean_button.clicked.connect(lambda: self.cleanup(False)) self.clean_button.clicked.connect(lambda: self.cleanup(False))
self.clean_layout.addWidget(self.clean_button) self.clean_layout.addWidget(self.clean_button)
@ -59,20 +59,20 @@ class LegendarySettings(QWidget):
self.core.lgd.config["Legendary"]["install_dir"] = self.select_path.text() self.core.lgd.config["Legendary"]["install_dir"] = self.select_path.text()
if self.select_path.text() == "" and "install_dir" in self.core.lgd.config["Legendary"].keys(): if self.select_path.text() == "" and "install_dir" in self.core.lgd.config["Legendary"].keys():
self.core.lgd.config["Legendary"].pop("install_dir") self.core.lgd.config["Legendary"].pop("install_dir")
logger.info("Remove install_dir section")
else: else:
logger.info("Set config install_dir to " + self.select_path.text()) logger.info("Set config install_dir to " + self.select_path.text())
self.core.lgd.save_config() self.core.lgd.save_config()
def max_worker_save(self, num_workers: str): def max_worker_save(self, num_workers: str):
self.core.lgd.config["Legendary"]["max_workers"] = num_workers
if num_workers == "": if num_workers == "":
self.core.lgd.config["Legendary"].pop("max_workers") self.core.lgd.config.remove_option("Legendary", "max_workers")
self.core.lgd.save_config()
return return
num_workers = int(num_workers) num_workers = int(num_workers)
if num_workers == 0: if num_workers == 0:
self.core.lgd.config["Legendary"].pop("max_workers") self.core.lgd.config.remove_option("Legendary", "max_workers")
logger.info("Updating config for max_workers") else:
self.core.lgd.config.set("Legendary", "max_workers", str(num_workers))
self.core.lgd.save_config() self.core.lgd.save_config()
def cleanup(self, keep_manifests): def cleanup(self, keep_manifests):
@ -92,7 +92,7 @@ class LegendarySettings(QWidget):
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 cleaned := (before-after) != 0: if cleaned := (before-after) > 0:
QMessageBox.information(self, "Cleanup", self.tr("Cleanup complete! Successfully removed {} MB").format(round(cleaned / 1024 / 1024, 3))) QMessageBox.information(self, "Cleanup", self.tr("Cleanup complete! Successfully removed {} MB").format(round(cleaned / 1024 / 1024, 3)))
else: else:
QMessageBox.information(self, "Cleanup", "Nothing to clean") QMessageBox.information(self, "Cleanup", "Nothing to clean")

View file

@ -3,7 +3,7 @@ import shutil
from logging import getLogger from logging import getLogger
from PyQt5.QtCore import QSettings from PyQt5.QtCore import QSettings
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QFileDialog, QComboBox, QPushButton, QCheckBox from PyQt5.QtWidgets import QVBoxLayout, QFileDialog, QComboBox, QPushButton, QCheckBox, QGroupBox
from Rare.Components.Tabs.Settings.SettingsWidget import SettingsWidget from Rare.Components.Tabs.Settings.SettingsWidget import SettingsWidget
from Rare.utils.QtExtensions import PathEdit from Rare.utils.QtExtensions import PathEdit
@ -12,21 +12,16 @@ from Rare.utils.utils import get_lang, get_possible_langs
logger = getLogger("RareSettings") logger = getLogger("RareSettings")
class RareSettings(QWidget): class RareSettings(QGroupBox):
def __init__(self): def __init__(self):
super(RareSettings, self).__init__() super(RareSettings, self).__init__()
self.setTitle(self.tr("Rare settings"))
self.setObjectName("group")
self.layout = QVBoxLayout() self.layout = QVBoxLayout()
self.title = QLabel("<h2>" + self.tr("Rare settings") + "</h2>")
self.layout.addWidget(self.title)
settings = QSettings() settings = QSettings()
img_dir = settings.value("img_dir", type=str) img_dir = settings.value("img_dir", os.path.expanduser("~/.cache/rare/images/"), type=str)
language = settings.value("language", type=str) language = settings.value("language", get_lang(), type=str)
# default settings
if not img_dir:
settings.setValue("img_dir", os.path.expanduser("~/.cache/rare/"))
if not language:
settings.setValue("language", get_lang())
del settings
# select Image dir # select Image dir
self.select_path = PathEdit(img_dir, type_of_file=QFileDialog.DirectoryOnly) self.select_path = PathEdit(img_dir, type_of_file=QFileDialog.DirectoryOnly)
self.select_path.text_edit.textChanged.connect(lambda t: self.save_path_button.setDisabled(False)) self.select_path.text_edit.textChanged.connect(lambda t: self.save_path_button.setDisabled(False))
@ -37,19 +32,27 @@ class RareSettings(QWidget):
# Select lang # Select lang
self.select_lang = QComboBox() self.select_lang = QComboBox()
languages = ["English", "Deutsch"] languages = ["English", "Deutsch", "Français"]
self.select_lang.addItems(languages) self.select_lang.addItems(languages)
if language in get_possible_langs(): if language in get_possible_langs():
if language == "de": if language == "de":
self.select_lang.setCurrentIndex(1) self.select_lang.setCurrentIndex(1)
elif language == "en": elif language == "en":
self.select_lang.setCurrentIndex(0) self.select_lang.setCurrentIndex(0)
elif language == "fr":
self.select_lang.setCurrentIndex(2)
else: else:
self.select_lang.setCurrentIndex(0) self.select_lang.setCurrentIndex(0)
self.lang_widget = SettingsWidget(self.tr("Language"), self.select_lang) self.lang_widget = SettingsWidget(self.tr("Language"), self.select_lang)
self.select_lang.currentIndexChanged.connect(self.update_lang) self.select_lang.currentIndexChanged.connect(self.update_lang)
self.layout.addWidget(self.lang_widget) self.layout.addWidget(self.lang_widget)
self.exit_to_sys_tray = QCheckBox(self.tr("Hide to System Tray Icon"))
self.exit_to_sys_tray.setChecked(settings.value("sys_tray", True, bool))
self.exit_to_sys_tray.stateChanged.connect(self.update_sys_tray)
self.sys_tray_widget = SettingsWidget(self.tr("Exit to System Tray Icon"), self.exit_to_sys_tray)
self.layout.addWidget(self.sys_tray_widget)
self.game_start_accept = QCheckBox(self.tr("Confirm launch of game")) self.game_start_accept = QCheckBox(self.tr("Confirm launch of game"))
self.game_start_accept.stateChanged.connect(self.update_start_confirm) self.game_start_accept.stateChanged.connect(self.update_start_confirm)
self.game_start_accept_widget = SettingsWidget(self.tr("Confirm launch of game"), self.game_start_accept) self.game_start_accept_widget = SettingsWidget(self.tr("Confirm launch of game"), self.game_start_accept)
@ -59,6 +62,10 @@ class RareSettings(QWidget):
self.setLayout(self.layout) self.setLayout(self.layout)
def update_sys_tray(self):
settings = QSettings()
settings.setValue("sys_tray", self.exit_to_sys_tray.isChecked())
def update_start_confirm(self): def update_start_confirm(self):
settings = QSettings() settings = QSettings()
settings.setValue("confirm_start", self.game_start_accept.isChecked()) settings.setValue("confirm_start", self.game_start_accept.isChecked())
@ -73,7 +80,8 @@ class RareSettings(QWidget):
settings.setValue("language", "en") settings.setValue("language", "en")
elif i == 1: elif i == 1:
settings.setValue("language", "de") settings.setValue("language", "de")
del settings elif i == 2:
settings.setValue("language", "fr")
self.lang_widget.info_text.setText(self.tr("Restart Application to activate changes")) self.lang_widget.info_text.setText(self.tr("Restart Application to activate changes"))
def update_path(self): def update_path(self):

View file

@ -1,22 +0,0 @@
import os
from PyQt5.QtWidgets import QTabWidget
from Rare.Components.Tabs.Settings.About import About
from Rare.Components.Tabs.Settings.Legendary import LegendarySettings
from Rare.Components.Tabs.Settings.Linux import LinuxSettings
from Rare.Components.Tabs.Settings.Rare import RareSettings
from Rare.utils.QtExtensions import SideTabBar
class SettingsTab(QTabWidget):
def __init__(self, core):
super(SettingsTab, self).__init__()
self.core = core
self.setTabBar(SideTabBar())
self.setTabPosition(QTabWidget.West)
self.addTab(RareSettings(), "Rare")
self.addTab(LegendarySettings(core), "Legendary")
if os.name != "nt":
self.addTab(LinuxSettings(core), "Linux")
self.addTab(About(), "About")

View file

@ -0,0 +1,24 @@
import os
from PyQt5.QtWidgets import QTabWidget
from Rare.Components.Tabs.Settings.About import About
from Rare.Components.Tabs.Settings.Legendary import LegendarySettings
from Rare.Components.Tabs.Settings.Linux import LinuxSettings
from Rare.Components.Tabs.Settings.Rare import RareSettings
from Rare.utils.QtExtensions import SideTabBar
class SettingsTab(QTabWidget):
def __init__(self, core):
super(SettingsTab, self).__init__()
self.core = core
self.setTabBar(SideTabBar())
self.setTabPosition(QTabWidget.West)
self.addTab(RareSettings(), "Rare")
self.addTab(LegendarySettings(core), "Legendary")
if os.name != "nt":
self.addTab(LinuxSettings(core), "Linux")
self.about = About()
self.addTab(self.about, "About (!)" if self.about.update_available else "About")

View file

@ -0,0 +1,22 @@
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtWidgets import QSystemTrayIcon, QMenu, QAction
from qtawesome import icon
class TrayIcon(QSystemTrayIcon):
def __init__(self, parent):
super(TrayIcon, self).__init__(parent)
self.setIcon(icon("ei.cogs", color="white"))
self.setVisible(True)
self.setToolTip("Rare")
self.menu = QMenu()
self.start_rare = QAction("Rare")
self.menu.addAction(self.start_rare)
self.exit_action = QAction(self.tr("Exit"))
self.menu.addSeparator()
self.menu.addAction(self.exit_action)
self.setContextMenu(self.menu)

View file

@ -2,21 +2,30 @@ import configparser
import logging import logging
import os import os
import sys import sys
import time
from PyQt5.QtCore import QSettings, QTranslator from PyQt5.QtCore import QSettings, QTranslator
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QApplication, QSystemTrayIcon
from Rare import lang_path, style_path from Rare import lang_path, style_path
from Rare.Components.Launch.LaunchDialog import LaunchDialog from Rare.Components.Launch.LaunchDialog import LaunchDialog
from Rare.Components.MainWindow import MainWindow from Rare.Components.MainWindow import MainWindow
from Rare.Components.TrayIcon import TrayIcon
from Rare.utils.utils import get_lang from Rare.utils.utils import get_lang
from custom_legendary.core import LegendaryCore from custom_legendary.core import LegendaryCore
start_time = time.strftime('%y-%m-%d--%H:%M') # year-month-day-hour-minute
file_name = os.path.expanduser(f"~/.cache/rare/logs/Rare_{start_time}.log")
if not os.path.exists(os.path.dirname(file_name)):
os.makedirs(os.path.dirname(file_name))
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,
filemode="w"
)
logger = logging.getLogger("Rare") logger = logging.getLogger("Rare")
@ -40,6 +49,7 @@ class App(QApplication):
self.core.lgd.save_config() self.core.lgd.save_config()
# set Application name for settings # set Application name for settings
self.mainwindow = None
self.setApplicationName("Rare") self.setApplicationName("Rare")
self.setOrganizationName("Rare") self.setOrganizationName("Rare")
settings = QSettings() settings = QSettings()
@ -58,6 +68,8 @@ class App(QApplication):
self.setStyleSheet(open(style_path + "RareStyle.qss").read()) self.setStyleSheet(open(style_path + "RareStyle.qss").read())
self.setWindowIcon(QIcon(style_path + "Logo.png")) self.setWindowIcon(QIcon(style_path + "Logo.png"))
# tray icon
# launch app # launch app
self.launch_dialog = LaunchDialog(self.core) self.launch_dialog = LaunchDialog(self.core)
self.launch_dialog.start_app.connect(self.start_app) self.launch_dialog.start_app.connect(self.start_app)
@ -65,10 +77,27 @@ class App(QApplication):
def start_app(self): def start_app(self):
self.mainwindow = MainWindow(self.core) self.mainwindow = MainWindow(self.core)
# close launch dialog after app widgets were created self.tray_icon = TrayIcon(self)
self.tray_icon.exit_action.triggered.connect(lambda: exit(0))
self.tray_icon.start_rare.triggered.connect(self.mainwindow.show)
self.tray_icon.activated.connect(self.tray)
self.mainwindow.tab_widget.downloadTab.finished.connect(lambda: self.tray_icon.showMessage(
self.tr("Download finished"), self.tr("Download finished. Game is playable now"),
QSystemTrayIcon.Information, 4000))
self.launch_dialog.close() self.launch_dialog.close()
def tray(self, reason):
if reason == QSystemTrayIcon.DoubleClick:
self.mainwindow.show()
logger.info("Show App")
def start(): def start():
app = App() while True:
app.exec_() app = App()
exit_code = app.exec_()
# if not restart
if exit_code != -133742:
break
# restart app
del app

View file

@ -30,8 +30,23 @@ QTabBar::tab:hover#main_tab_bar {
} }
QGroupBox{
padding: 4px;
margin: 8px;
}
QGroupBox#game_widget_icon{
border: none;
padding: 0;
margin: 0;
}
QGroupBox#group{ QGroupBox#group{
font-size: 15px; font-size: 15px;
font-weight: bold;
border: 1px solid white;
margin-top: 10px;
padding: 8px;
} }
QTabBar::tab:disabled { QTabBar::tab:disabled {

View file

@ -1,5 +1,5 @@
import os import os
__version__ = "0.9.8.3" __version__ = "0.9.9"
style_path = os.path.join(os.path.dirname(__file__), "Styles/") style_path = os.path.join(os.path.dirname(__file__), "Styles/")
lang_path = os.path.join(os.path.dirname(__file__), "languages/") lang_path = os.path.join(os.path.dirname(__file__), "languages/")

View file

@ -14,20 +14,3 @@ def main():
if __name__ == '__main__': if __name__ == '__main__':
main() main()
"""
tray = QSystemTrayIcon()
tray.setIcon(icon("fa.gamepad", color="white"))
tray.setVisible(True)
menu = QMenu()
option1 = QAction("Geeks for Geeks")
option1.triggered.connect(lambda: app.exec_())
option2 = QAction("GFG")
menu.addAction(option1)
menu.addAction(option2)
# To quit the app
quit = QAction("Quit")
quit.triggered.connect(app.quit)
menu.addAction(quit)
# Adding options to the System Tray
tray.setContextMenu(menu)"""

Binary file not shown.

View file

@ -4,12 +4,12 @@
<context> <context>
<name>About</name> <name>About</name>
<message> <message>
<location filename="../Components/Tabs/Settings/About.py" line="17"/> <location filename="../Components/Tabs/Settings/About.py" line="29"/>
<source>Developer:</source> <source>Developer:</source>
<translation>Entwickler:</translation> <translation>Entwickler:</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Settings/About.py" line="22"/> <location filename="../Components/Tabs/Settings/About.py" line="34"/>
<source>Legendary developer:</source> <source>Legendary developer:</source>
<translation>Legendary Entwickler:</translation> <translation>Legendary Entwickler:</translation>
</message> </message>
@ -19,15 +19,38 @@
<translation type="obsolete">Dies ist eine beta version, also können Bugs entstehen. Wenn du einen Bug bemerkst, kontaktiere mich, indem du einen Issue auf &lt;a href=&apos;https://github.com/Dummerle/Rare/issues&apos;&gt;Github&lt;/a&gt; erstellst oder mir auf Discord eine Nachricht schickst. Ebenso bei einem Wunsch für Features</translation> <translation type="obsolete">Dies ist eine beta version, also können Bugs entstehen. Wenn du einen Bug bemerkst, kontaktiere mich, indem du einen Issue auf &lt;a href=&apos;https://github.com/Dummerle/Rare/issues&apos;&gt;Github&lt;/a&gt; erstellst oder mir auf Discord eine Nachricht schickst. Ebenso bei einem Wunsch für Features</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Settings/About.py" line="28"/> <location filename="../Components/Tabs/Settings/About.py" line="40"/>
<source>This is a beta version, so you can get bugs. If you get a bug, please report it by creating a Issue on &lt;a href=&apos;https://github.com/Dummerle/Rare/issues&apos;&gt;Github&lt;/a&gt;. You can also contact me on Discord (Dummerle#7419). Or you can join the &lt;a href=&apos;https://discord.gg/YvmABK9YSk&apos;&gt;Discord server&lt;/a&gt;</source> <source>This is a beta version, so you can get bugs. If you get a bug, please report it by creating a Issue on &lt;a href=&apos;https://github.com/Dummerle/Rare/issues&apos;&gt;Github&lt;/a&gt;. You can also contact me on Discord (Dummerle#7419). Or you can join the &lt;a href=&apos;https://discord.gg/YvmABK9YSk&apos;&gt;Discord server&lt;/a&gt;</source>
<translation>Dies ist eine Betaversion, also können Bugs und andere Unschönheiten auftreten. Falls ein Bug auftritt, bitte auf &lt;a href=&apos;https://github.com/Dummerle/Rare/issues&apos;&gt;Github&lt;/a&gt; melden, indem du einen Issue erstellst oder auf Discord. (Dummerle#7419). Ein Rare &lt;a href=&apos;https://discord.gg/YvmABK9YSk&apos;&gt;Discord server&lt;/a&gt; existiert ebenfalls</translation> <translation>Dies ist eine Betaversion, also können Bugs und andere Unschönheiten auftreten. Falls ein Bug auftritt, bitte auf &lt;a href=&apos;https://github.com/Dummerle/Rare/issues&apos;&gt;Github&lt;/a&gt; melden, indem du einen Issue erstellst oder auf Discord. (Dummerle#7419). Ein Rare &lt;a href=&apos;https://discord.gg/YvmABK9YSk&apos;&gt;Discord server&lt;/a&gt; existiert ebenfalls</translation>
</message> </message>
<message>
<location filename="../Components/Tabs/Settings/About.py" line="23"/>
<source>Update available: {} -&gt; {}</source>
<translation>Update verfügbar: {} -&gt; {}</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/About.py" line="25"/>
<source>Download latest release</source>
<translation>Neueste Version herunterladen</translation>
</message>
</context>
<context>
<name>App</name>
<message>
<location filename="../Main.py" line="84"/>
<source>Download finished</source>
<translation>Download abgeschlossen</translation>
</message>
<message>
<location filename="../Main.py" line="84"/>
<source>Download finished. Game is playable now</source>
<translation>Downlaod abgeschlossen. Spiel kann jetzt gespielt werden</translation>
</message>
</context> </context>
<context> <context>
<name>BaseInstalledWidget</name> <name>BaseInstalledWidget</name>
<message> <message>
<location filename="../Components/Tabs/Games/GameWidgets/BaseInstalledWidget.py" line="34"/> <location filename="../Components/Tabs/Games/GameWidgets/BaseInstalledWidget.py" line="36"/>
<source>Do you want to launch {}</source> <source>Do you want to launch {}</source>
<translation>Möchtest du {} starten</translation> <translation>Möchtest du {} starten</translation>
</message> </message>
@ -55,68 +78,129 @@
<translation>Laden...</translation> <translation>Laden...</translation>
</message> </message>
</context> </context>
<context>
<name>DlQueueWidget</name>
<message>
<location filename="../Components/Tabs/Downloads/DlQueueWidget.py" line="63"/>
<source>Download Queue</source>
<translation>Eingereihte Downloads</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DlQueueWidget.py" line="79"/>
<source>No downloads in queue</source>
<translation>Keine eingereihten Downloads</translation>
</message>
</context>
<context>
<name>DlWidget</name>
<message>
<location filename="../Components/Tabs/Downloads/DlQueueWidget.py" line="44"/>
<source>Download size: {} GB</source>
<translation>Download Größe: {} GB</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DlQueueWidget.py" line="45"/>
<source>Install size: {} GB</source>
<translation>Installierte Größe: {} GB</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DlQueueWidget.py" line="48"/>
<source>Remove Download</source>
<translation>Download löschen</translation>
</message>
</context>
<context> <context>
<name>DownloadTab</name> <name>DownloadTab</name>
<message> <message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="133"/> <location filename="../Components/Tabs/Downloads/DownloadTab.py" line="32"/>
<source>No active Download</source> <source>No active Download</source>
<translation>Kein aktiver Download</translation> <translation>Kein aktiver Download</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="154"/> <location filename="../Components/Tabs/Downloads/DownloadTab.py" line="53"/>
<source>Stop Download</source> <source>Stop Download</source>
<translation>Download anhalten</translation> <translation>Download anhalten</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="170"/> <location filename="../Components/Tabs/Downloads/DownloadTab.py" line="69"/>
<source>No updates available</source> <source>No updates available</source>
<translation>Keine Updates verfügbar</translation> <translation>Keine Updates verfügbar</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="217"/> <location filename="../Components/Tabs/Downloads/DownloadTab.py" line="122"/>
<source>Error preparing download</source> <source>Error preparing download</source>
<translation>Fehler beim Vorbereiten des Downloads</translation> <translation>Fehler beim Vorbereiten des Downloads</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="222"/> <location filename="../Components/Tabs/Downloads/DownloadTab.py" line="127"/>
<source>Download size is 0. Game already exists</source> <source>Download size is 0. Game already exists</source>
<translation>Die Größe des Downloads ist 0. Spiel existiert bereits</translation> <translation>Die Größe des Downloads ist 0. Spiel existiert bereits</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="287"/> <location filename="../Components/Tabs/Downloads/DownloadTab.py" line="201"/>
<source>Installation finished</source> <source>Installation finished</source>
<translation>Installation abgeschlossen</translation> <translation>Installation abgeschlossen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="311"/> <location filename="../Components/Tabs/Downloads/DownloadTab.py" line="240"/>
<source>Installing Game: No active download</source> <source>Installing Game: No active download</source>
<translation>Installierendes Spiel: Kein aktiver Download</translation> <translation>Installierendes Spiel: Kein aktiver Download</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="319"/> <location filename="../Components/Tabs/Downloads/DownloadTab.py" line="248"/>
<source>Download speed</source> <source>Download speed</source>
<translation>Geschwindigkeit</translation> <translation>Geschwindigkeit</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="320"/> <location filename="../Components/Tabs/Downloads/DownloadTab.py" line="249"/>
<source>Cache used</source> <source>Cache used</source>
<translation>Benutzter Cache</translation> <translation>Benutzter Cache</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="321"/> <location filename="../Components/Tabs/Downloads/DownloadTab.py" line="250"/>
<source>Downloaded</source> <source>Downloaded</source>
<translation>Runtergeladen</translation> <translation>Runtergeladen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="322"/> <location filename="../Components/Tabs/Downloads/DownloadTab.py" line="251"/>
<source>Time left: </source> <source>Time left: </source>
<translation>Zeit übrig: </translation> <translation>Zeit übrig: </translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="288"/> <location filename="../Components/Tabs/Downloads/DownloadTab.py" line="202"/>
<source>Finished Download of game {}</source> <source>Finished Download of game {}</source>
<translation>Downlaod von {} abgeschlossen</translation> <translation>Downlaod von {} abgeschlossen</translation>
</message> </message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="193"/>
<source>Download finished. Reload library</source>
<translation>Download abgeschlossen. Spiele neu laden</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="332"/>
<source>Download queue: Empty</source>
<translation type="obsolete">Anschließende Downloads: Keine</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="238"/>
<source>Download queue: </source>
<translation type="obsolete">Anschließende Downloads: </translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="238"/>
<source>Empty</source>
<translation type="obsolete">Keine</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="149"/>
<source>Installing Game: </source>
<translation>Installierendes Spiel: </translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="64"/>
<source>Updates</source>
<translation>Updates</translation>
</message>
</context> </context>
<context> <context>
<name>DxvkWidget</name> <name>DxvkWidget</name>
@ -159,32 +243,32 @@
<context> <context>
<name>GameActions</name> <name>GameActions</name>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="182"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="189"/>
<source>Uninstall game</source> <source>Uninstall game</source>
<translation>Spiel deinstallieren</translation> <translation>Spiel deinstallieren</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="184"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="191"/>
<source>Uninstall</source> <source>Uninstall</source>
<translation>Deinstallieren</translation> <translation>Deinstallieren</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="190"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="197"/>
<source>Verify Game</source> <source>Verify Game</source>
<translation>Spieldateien überprüfen</translation> <translation>Spieldateien überprüfen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="195"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="202"/>
<source>Verify</source> <source>Verify</source>
<translation>Überprüfen</translation> <translation>Überprüfen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="204"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="211"/>
<source>Repair Game</source> <source>Repair Game</source>
<translation>Spiel reparieren</translation> <translation>Spiel reparieren</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="206"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="213"/>
<source>Repair</source> <source>Repair</source>
<translation>Reparieren</translation> <translation>Reparieren</translation>
</message> </message>
@ -192,50 +276,50 @@
<context> <context>
<name>GameInfo</name> <name>GameInfo</name>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="106"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="113"/>
<source>Repair file does not exist or game does not need a repair. Please verify game first</source> <source>Repair file does not exist or game does not need a repair. Please verify game first</source>
<translation>Reparationsdatei existiert nicht oder das Spiel braucht keine Reperatur. Bitte das spiel zuerst überprüfen</translation> <translation>Reparationsdatei existiert nicht oder das Spiel braucht keine Reperatur. Bitte das spiel zuerst überprüfen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="131"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="138"/>
<source>Verification failed, {} file(s) corrupted, {} file(s) are missing. Do you want to repair them?</source> <source>Verification failed, {} file(s) corrupted, {} file(s) are missing. Do you want to repair them?</source>
<translation>Überprüfung fehlgeschlagen, {} Datei(en) fehlerhaft, {} Datei(en) fehlen. Willst du das Spiel reparieren?</translation> <translation>Überprüfung fehlgeschlagen, {} Datei(en) fehlerhaft, {} Datei(en) fehlen. Willst du das Spiel reparieren?</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="160"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="167"/>
<source>Developer: </source> <source>Developer: </source>
<translation>Entwickler: </translation> <translation>Entwickler: </translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="161"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="168"/>
<source>Install size: </source> <source>Install size: </source>
<translation>Größe: </translation> <translation>Größe: </translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="163"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="170"/>
<source>Install path: </source> <source>Install path: </source>
<translation>Installationsordner: </translation> <translation>Installationsordner: </translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="98"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="98"/>
<source>Are you sure to uninstall {}</source> <source>Are you sure to uninstall {}</source>
<translation>Möchtest du {} wirklich deinstallieren</translation> <translation type="obsolete">Möchtest du {} wirklich deinstallieren</translation>
</message> </message>
</context> </context>
<context> <context>
<name>GameList</name> <name>GameList</name>
<message> <message>
<location filename="../Components/Tabs/Games/GameList.py" line="148"/> <location filename="../Components/Tabs/Games/GameList.py" line="160"/>
<source>Launch</source> <source>Launch</source>
<translation>Starten</translation> <translation>Starten</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameList.py" line="153"/> <location filename="../Components/Tabs/Games/GameList.py" line="165"/>
<source>Game running</source> <source>Game running</source>
<translation>Spiel läuft</translation> <translation>Spiel läuft</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameList.py" line="45"/> <location filename="../Components/Tabs/Games/GameList.py" line="46"/>
<source>Installed Games: {} Available Games: {}</source> <source>Installed Games: {} Available Games: {}</source>
<translation>Installierte Spiele: {} Verfügbare Spiele: {}</translation> <translation>Installierte Spiele: {} Verfügbare Spiele: {}</translation>
</message> </message>
@ -243,17 +327,17 @@
<context> <context>
<name>GameListHeadBar</name> <name>GameListHeadBar</name>
<message> <message>
<location filename="../Components/Tabs/Games/GamesTab.py" line="74"/> <location filename="../Components/Tabs/Games/__init__.py" line="74"/>
<source>Installed only</source> <source>Installed only</source>
<translation>Nur Installierte</translation> <translation>Nur Installierte</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GamesTab.py" line="81"/> <location filename="../Components/Tabs/Games/__init__.py" line="81"/>
<source>Import Game</source> <source>Import Game</source>
<translation>Spiel importieren</translation> <translation>Spiel importieren</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GamesTab.py" line="93"/> <location filename="../Components/Tabs/Games/__init__.py" line="93"/>
<source>Search Game</source> <source>Search Game</source>
<translation>Spiel suchen</translation> <translation>Spiel suchen</translation>
</message> </message>
@ -322,15 +406,20 @@
<context> <context>
<name>IconWidgetUninstalled</name> <name>IconWidgetUninstalled</name>
<message> <message>
<location filename="../Components/Tabs/Games/GameWidgets/UninstalledIconWidget.py" line="43"/> <location filename="../Components/Tabs/Games/GameWidgets/UninstalledIconWidget.py" line="45"/>
<source>Install Game</source> <source>Install Game</source>
<translation>Spiel installieren</translation> <translation>Spiel installieren</translation>
</message> </message>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/UninstalledIconWidget.py" line="47"/>
<source>Installation running</source>
<translation>Installation läuft</translation>
</message>
</context> </context>
<context> <context>
<name>ImportWidget</name> <name>ImportWidget</name>
<message> <message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="99"/> <location filename="../Components/Dialogs/Login/ImportWidget.py" line="102"/>
<source>Import</source> <source>Import</source>
<translation>Importieren</translation> <translation>Importieren</translation>
</message> </message>
@ -355,27 +444,27 @@
<translation>Laden...</translation> <translation>Laden...</translation>
</message> </message>
<message> <message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="98"/> <location filename="../Components/Dialogs/Login/ImportWidget.py" line="101"/>
<source>Error: No valid session found</source> <source>Error: No valid session found</source>
<translation>Keine valide Session gefunden</translation> <translation>Keine valide Session gefunden</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="26"/> <location filename="../Components/Tabs/Games/ImportWidget.py" line="27"/>
<source>Back</source> <source>Back</source>
<translation>Zurück</translation> <translation>Zurück</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="40"/> <location filename="../Components/Tabs/Games/ImportWidget.py" line="43"/>
<source>Select path to game</source> <source>Select path to game</source>
<translation>Wähle den Pfad zum Spiel</translation> <translation>Wähle den Pfad zum Spiel</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="62"/> <location filename="../Components/Tabs/Games/ImportWidget.py" line="65"/>
<source>Import Game</source> <source>Import Game</source>
<translation>Spiel importieren</translation> <translation>Spiel importieren</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="70"/> <location filename="../Components/Tabs/Games/ImportWidget.py" line="77"/>
<source>Import all games from Epic Games Launcher</source> <source>Import all games from Epic Games Launcher</source>
<translation>Alle Spiele aus dem Epic Games Launcher importieren</translation> <translation>Alle Spiele aus dem Epic Games Launcher importieren</translation>
</message> </message>
@ -385,45 +474,55 @@
<translation type="obsolete">{} Spiele erfolgreich importiert</translation> <translation type="obsolete">{} Spiele erfolgreich importiert</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="43"/> <location filename="../Components/Tabs/Games/ImportWidget.py" line="46"/>
<source>Override app name (Only if imported game from legendary or the app could not find the app name)</source> <source>Override app name (Only if imported game from legendary or the app could not find the app name)</source>
<translation>App Name überschreiben (Nur falls das Spiel von Legendary importiert wird oder der App Name nicht gefunden wird</translation> <translation>App Name überschreiben (Nur falls das Spiel von Legendary importiert wird oder der App Name nicht gefunden wird</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="110"/> <location filename="../Components/Tabs/Games/ImportWidget.py" line="118"/>
<source>Could not find app name</source> <source>Could not find app name</source>
<translation>Konnte den Appnamen nicht finden</translation> <translation>Konnte den Appnamen nicht finden</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="114"/> <location filename="../Components/Tabs/Games/ImportWidget.py" line="122"/>
<source>Successfully imported {}. Reload library</source> <source>Successfully imported {}. Reload library</source>
<translation>Erfolgreich {} importiert. Spiele neu laden</translation> <translation>Erfolgreich {} importiert. Spiele neu laden</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="120"/> <location filename="../Components/Tabs/Games/ImportWidget.py" line="128"/>
<source>Failed to import {}</source> <source>Failed to import {}</source>
<translation>{} Konnte nicht importiert werden</translation> <translation>{} Konnte nicht importiert werden</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="159"/> <location filename="../Components/Tabs/Games/ImportWidget.py" line="167"/>
<source>Successfully imported {} Games. Reloading Library</source> <source>Successfully imported {} Games. Reloading Library</source>
<translation>Erfolgreich {} Spiele importiert. Spiele neu laden</translation> <translation>Erfolgreich {} Spiele importiert. Spiele neu laden</translation>
</message> </message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="39"/>
<source>Import existing game from Epic Games Launcher</source>
<translation>Ein bereits existierendes Spiel aus dem Epic Games Launcher importieren</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="170"/>
<source>No Games were found</source>
<translation>Keine Spiele wurden gefunden</translation>
</message>
</context> </context>
<context> <context>
<name>InfoTabs</name> <name>InfoTabs</name>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="26"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="27"/>
<source>Back</source> <source>Back</source>
<translation>Zurück</translation> <translation>Zurück</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="30"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="31"/>
<source>Game Info</source> <source>Game Info</source>
<translation>Spielinfo</translation> <translation>Spielinfo</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="32"/> <location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="33"/>
<source>Settings</source> <source>Settings</source>
<translation>Einstellungen</translation> <translation>Einstellungen</translation>
</message> </message>
@ -431,27 +530,42 @@
<context> <context>
<name>InstallDialog</name> <name>InstallDialog</name>
<message> <message>
<location filename="../Components/Dialogs/InstallDialog.py" line="22"/> <location filename="../Components/Dialogs/InstallDialog.py" line="29"/>
<source>Max workers (0: Default)</source> <source>Max workers (0: Default)</source>
<translation>Maximale Anzahl Downloadprozessen(Standard: 0)</translation> <translation>Maximale Anzahl Downloadprozessen(Standard: 0)</translation>
</message> </message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="20"/>
<source>&lt;h3&gt;Install {}&lt;/h3&gt;</source>
<translation>&lt;h3&gt;Installiere {}&lt;/h3&gt;</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="33"/>
<source>Force download</source>
<translation>Download erzwingen</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="37"/>
<source>Ignore free space (Warning!)</source>
<translation>Freien Speicherplatz ignorieren (Achtung!)</translation>
</message>
</context> </context>
<context> <context>
<name>InstallInfoDialog</name> <name>InstallInfoDialog</name>
<message> <message>
<location filename="../Components/Dialogs/InstallDialog.py" line="55"/> <location filename="../Components/Dialogs/InstallDialog.py" line="70"/>
<source>Download size: {}GB <source>Download size: {}GB
Install size: {}GB</source> Install size: {}GB</source>
<translation>Downloadgröße: {}GB <translation>Downloadgröße: {}GB
Installationsgröße: {} GB</translation> Installationsgröße: {} GB</translation>
</message> </message>
<message> <message>
<location filename="../Components/Dialogs/InstallDialog.py" line="61"/> <location filename="../Components/Dialogs/InstallDialog.py" line="76"/>
<source>Install</source> <source>Install</source>
<translation>Installieren</translation> <translation>Installieren</translation>
</message> </message>
<message> <message>
<location filename="../Components/Dialogs/InstallDialog.py" line="63"/> <location filename="../Components/Dialogs/InstallDialog.py" line="78"/>
<source>Cancel</source> <source>Cancel</source>
<translation>Abbruch</translation> <translation>Abbruch</translation>
</message> </message>
@ -503,17 +617,17 @@ Installationsgröße: {} GB</translation>
<context> <context>
<name>LegendarySettings</name> <name>LegendarySettings</name>
<message> <message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="18"/> <location filename="../Components/Tabs/Settings/Legendary.py" line="16"/>
<source>Legendary settings</source> <source>Legendary settings</source>
<translation>Legendary Einstellungen</translation> <translation>Legendary Einstellungen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="27"/> <location filename="../Components/Tabs/Settings/Legendary.py" line="28"/>
<source>Default installation directory</source> <source>Default installation directory</source>
<translation>Standardordner für Installationen</translation> <translation>Standardordner für Installationen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="36"/> <location filename="../Components/Tabs/Settings/Legendary.py" line="37"/>
<source>Max workers for Download (Less: slower download)(0: Default)</source> <source>Max workers for Download (Less: slower download)(0: Default)</source>
<translation>Maximale Anzahl Downloadprozesse (Weniger: langsamer)(Standard: 0)</translation> <translation>Maximale Anzahl Downloadprozesse (Weniger: langsamer)(Standard: 0)</translation>
</message> </message>
@ -582,6 +696,14 @@ Installationsgröße: {} GB</translation>
<translation>Dies öffnet den Browser, Einloggen und den Text kopieren</translation> <translation>Dies öffnet den Browser, Einloggen und den Text kopieren</translation>
</message> </message>
</context> </context>
<context>
<name>MainWindow</name>
<message>
<location filename="../Components/MainWindow.py" line="21"/>
<source>There is a download active. Do you really want to exit app?</source>
<translation>Ein Download läuft noch. Möchtest du die App wirklich beenden?</translation>
</message>
</context>
<context> <context>
<name>MiniWidget</name> <name>MiniWidget</name>
<message> <message>
@ -595,12 +717,12 @@ Installationsgröße: {} GB</translation>
<translation>Accounteinstellungen</translation> <translation>Accounteinstellungen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="26"/> <location filename="../Components/Tabs/Account/AccountWidget.py" line="27"/>
<source>Logout</source> <source>Logout</source>
<translation>Ausloggen</translation> <translation>Ausloggen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="32"/> <location filename="../Components/Tabs/Account/AccountWidget.py" line="33"/>
<source>Do you really want to logout</source> <source>Do you really want to logout</source>
<translation>Willst du dich wirklich abmelden</translation> <translation>Willst du dich wirklich abmelden</translation>
</message> </message>
@ -629,65 +751,75 @@ Installationsgröße: {} GB</translation>
<context> <context>
<name>RareSettings</name> <name>RareSettings</name>
<message> <message>
<location filename="../Components/Tabs/Settings/Rare.py" line="19"/> <location filename="../Components/Tabs/Settings/Rare.py" line="18"/>
<source>Rare settings</source> <source>Rare settings</source>
<translation>Rare Einstellungen</translation> <translation>Rare Einstellungen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Settings/Rare.py" line="33"/> <location filename="../Components/Tabs/Settings/Rare.py" line="28"/>
<source>Save</source> <source>Save</source>
<translation>Speichern</translation> <translation>Speichern</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Settings/Rare.py" line="35"/> <location filename="../Components/Tabs/Settings/Rare.py" line="30"/>
<source>Image Directory</source> <source>Image Directory</source>
<translation>Ordner für Bilder</translation> <translation>Ordner für Bilder</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Settings/Rare.py" line="49"/> <location filename="../Components/Tabs/Settings/Rare.py" line="44"/>
<source>Language</source> <source>Language</source>
<translation>Sprache</translation> <translation>Sprache</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Settings/Rare.py" line="77"/> <location filename="../Components/Tabs/Settings/Rare.py" line="82"/>
<source>Restart Application to activate changes</source> <source>Restart Application to activate changes</source>
<translation>Starte die App neu um die Änderungen zu aktivieren</translation> <translation>Starte die App neu um die Änderungen zu aktivieren</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/Settings/Rare.py" line="55"/> <location filename="../Components/Tabs/Settings/Rare.py" line="56"/>
<source>Confirm launch of game</source> <source>Confirm launch of game</source>
<translation>Start des Spiels bestätigen</translation> <translation>Start des Spiels bestätigen</translation>
</message> </message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="51"/>
<source>Exit to System Tray Icon</source>
<translation>Beim verlassen auf das System Tray Icon minimieren</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="48"/>
<source>Hide to System Tray Icon</source>
<translation>In das System Tray Icon minimieren</translation>
</message>
</context> </context>
<context> <context>
<name>SyncSaves</name> <name>SyncSaves</name>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="65"/> <location filename="../Components/Tabs/CloudSaves/__init__.py" line="62"/>
<source>Cloud Saves</source> <source>Cloud Saves</source>
<translation>Cloud Speicherstände</translation> <translation>Cloud Speicherstände</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="54"/> <location filename="../Components/Tabs/CloudSaves/__init__.py" line="52"/>
<source>Found Saves for folowing Games</source> <source>Found Saves for folowing Games</source>
<translation>Spielstände für folgende Spiele gefunden</translation> <translation>Spielstände für folgende Spiele gefunden</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="65"/> <location filename="../Components/Tabs/CloudSaves/__init__.py" line="62"/>
<source>Your games does not support Cloud Saves</source> <source>Your games does not support Cloud Saves</source>
<translation>Deine Spiele unterstützen keine Online Speicherstände</translation> <translation>Deine Spiele unterstützen keine Online Speicherstände</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="70"/> <location filename="../Components/Tabs/CloudSaves/__init__.py" line="67"/>
<source>Sync all games</source> <source>Sync all games</source>
<translation>Alle Spiele synchronisieren</translation> <translation>Alle Spiele synchronisieren</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="110"/> <location filename="../Components/Tabs/CloudSaves/__init__.py" line="108"/>
<source>Found no savepath</source> <source>Found no savepath</source>
<translation>Kein Speicherort gefunden</translation> <translation>Kein Speicherort gefunden</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="110"/> <location filename="../Components/Tabs/CloudSaves/__init__.py" line="108"/>
<source>No save path was found. Please select path or skip</source> <source>No save path was found. Please select path or skip</source>
<translation>Kein Speicherort wurde gefunden. Wähle einen Ordner oder überspringe</translation> <translation>Kein Speicherort wurde gefunden. Wähle einen Ordner oder überspringe</translation>
</message> </message>
@ -695,97 +827,97 @@ Installationsgröße: {} GB</translation>
<context> <context>
<name>SyncWidget</name> <name>SyncWidget</name>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="60"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="62"/>
<source>Path not found</source> <source>Path not found</source>
<translation>Ordner nicht gefunden</translation> <translation>Ordner nicht gefunden</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="76"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="78"/>
<source>Local Save date: </source> <source>Local Save date: </source>
<translation>Lokales Speicherdatum: </translation> <translation>Lokales Speicherdatum: </translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="79"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="81"/>
<source>No Local Save files</source> <source>No Local Save files</source>
<translation>Keine Lokalen Dateien</translation> <translation>Keine Lokalen Dateien</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="81"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="83"/>
<source>Cloud save date: </source> <source>Cloud save date: </source>
<translation>Online Speicherdatum: </translation> <translation>Online Speicherdatum: </translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="83"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="85"/>
<source>No Cloud saves</source> <source>No Cloud saves</source>
<translation>Keine Online Speicherstände</translation> <translation>Keine Online Speicherstände</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="87"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="89"/>
<source>Game is up to date</source> <source>Game is up to date</source>
<translation>Spiel ist aktuell</translation> <translation>Spiel ist aktuell</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="88"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="90"/>
<source>Upload anyway</source> <source>Upload anyway</source>
<translation>Trotzdem hochladen</translation> <translation>Trotzdem hochladen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="89"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="91"/>
<source>Download anyway</source> <source>Download anyway</source>
<translation>Trotzdem herunterladen</translation> <translation>Trotzdem herunterladen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="91"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="93"/>
<source>Cloud save is newer</source> <source>Cloud save is newer</source>
<translation>Online Speicherstand ist aktueller</translation> <translation>Online Speicherstand ist aktueller</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="92"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="94"/>
<source>Download Cloud saves</source> <source>Download Cloud saves</source>
<translation>Online Speicherstand herunterladen</translation> <translation>Online Speicherstand herunterladen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="96"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="98"/>
<source>Upload Saves</source> <source>Upload Saves</source>
<translation>Spielstände hochladen</translation> <translation>Spielstände hochladen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="107"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="109"/>
<source>Local save is newer</source> <source>Local save is newer</source>
<translation>Lokaler Speicher ist aktueller</translation> <translation>Lokaler Speicher ist aktueller</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="108"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="110"/>
<source>Upload saves</source> <source>Upload saves</source>
<translation>Spielstände hochladen</translation> <translation>Spielstände hochladen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="112"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="114"/>
<source>Download saves</source> <source>Download saves</source>
<translation>Spielstand herunterladen</translation> <translation>Spielstand herunterladen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="134"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="137"/>
<source>Change path</source> <source>Change path</source>
<translation>Pfad ändern</translation> <translation>Pfad ändern</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="159"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="162"/>
<source>Uploading...</source> <source>Uploading...</source>
<translation>Hochladen...</translation> <translation>Hochladen...</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="167"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="170"/>
<source>Upload finished</source> <source>Upload finished</source>
<translation>Hochladen abgeschlossen</translation> <translation>Hochladen abgeschlossen</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="177"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="180"/>
<source>Downloading...</source> <source>Downloading...</source>
<translation>Runterladen...</translation> <translation>Runterladen...</translation>
</message> </message>
<message> <message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="183"/> <location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="186"/>
<source>Download finished</source> <source>Download finished</source>
<translation>Download abgeschlossen</translation> <translation>Download abgeschlossen</translation>
</message> </message>
@ -798,10 +930,46 @@ Installationsgröße: {} GB</translation>
<translation>Spiele</translation> <translation>Spiele</translation>
</message> </message>
</context> </context>
<context>
<name>TrayIcon</name>
<message>
<location filename="../Components/TrayIcon.py" line="18"/>
<source>Exit</source>
<translation>Schließen</translation>
</message>
</context>
<context>
<name>UninstallDialog</name>
<message>
<location filename="../Components/Dialogs/UninstallDialog.py" line="13"/>
<source>Do you really want to uninstall {}</source>
<translation>Möchtest du wirklich {} deinstallieren</translation>
</message>
<message>
<location filename="../Components/Dialogs/UninstallDialog.py" line="15"/>
<source>Keep Files</source>
<translation>Dateien behalten</translation>
</message>
<message>
<location filename="../Components/Dialogs/UninstallDialog.py" line="18"/>
<source>Do you want to keep files?</source>
<translation>Willst du die Dateien behalten?</translation>
</message>
<message>
<location filename="../Components/Dialogs/UninstallDialog.py" line="22"/>
<source>Uninstall</source>
<translation>Deinstallieren</translation>
</message>
<message>
<location filename="../Components/Dialogs/UninstallDialog.py" line="25"/>
<source>Cancel</source>
<translation>Abbruch</translation>
</message>
</context>
<context> <context>
<name>UpdateWidget</name> <name>UpdateWidget</name>
<message> <message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="349"/> <location filename="../Components/Tabs/Downloads/DownloadTab.py" line="277"/>
<source>Update Game</source> <source>Update Game</source>
<translation>Spiel updaten</translation> <translation>Spiel updaten</translation>
</message> </message>

BIN
Rare/languages/fr.qm Normal file

Binary file not shown.

818
Rare/languages/fr.ts Normal file
View file

@ -0,0 +1,818 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS><TS version="2.0">
<context>
<name>About</name>
<message>
<location filename="../Components/Tabs/Settings/About.py" line="17"/>
<source>Developer:</source>
<translation>Développeur</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/About.py" line="22"/>
<source>Legendary developer:</source>
<translation>Legendary Développeur</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/About.py" line="28"/>
<source>This is a beta version, so you can get bugs. If you get a bug, please report it by creating a Issue on &lt;a href=&apos;https://github.com/Dummerle/Rare/issues&apos;&gt;Github&lt;/a&gt;. You can also contact me on Discord (Dummerle#7419). Or you can join the &lt;a href=&apos;https://discord.gg/YvmABK9YSk&apos;&gt;Discord server&lt;/a&gt;</source>
<translation>Il s'agit d'une version bêta, vous pouvez donc rencontrer des bogues. Si vous rencontrez un bug, veuillez le signaler en créant un Issue sur &lt;a href=&apos;https://github.com/Dummerle/Rare/issues&apos;&gt;Github&lt;/a&gt;. Vous pouvez également me contacter sur Discord (Dummerle#7419). Ou vous pouvez rejoindre le &lt;a href=&apos;https://discord.gg/YvmABK9YSk&apos;&gt;serveur Discord&lt;/a&gt;</translation>
</message>
</context>
<context>
<name>BaseInstalledWidget</name>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/BaseInstalledWidget.py" line="34"/>
<source>Do you want to launch {}</source>
<translation>Voulez-vous lancer {}</translation>
</message>
</context>
<context>
<name>BrowserLogin</name>
<message>
<location filename="../Components/Dialogs/Login/BrowserLogin.py" line="24"/>
<source>Opens a browser. You login and copy the json code in the field below. Click &lt;a href=&apos;{}&apos;&gt;here&lt;/a&gt; to open Browser</source>
<translation>Ouvre un navigateur. Vous vous connectez et copiez le code json dans le champ ci-dessous. Cliquez &lt;a href=&apos;{}&apos;&gt;here&lt;/a&gt; pour ouvrir un navigateur</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/BrowserLogin.py" line="32"/>
<source>Insert SID here</source>
<translation>Insérer le SID ici</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/BrowserLogin.py" line="37"/>
<source>Login</source>
<translation>Login</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/BrowserLogin.py" line="44"/>
<source>Loading...</source>
<translation>charge...</translation>
</message>
</context>
<context>
<name>DownloadTab</name>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="133"/>
<source>No active Download</source>
<translation>Aucun téléchargement actif</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="154"/>
<source>Stop Download</source>
<translation>Stop Télécharger</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="170"/>
<source>No updates available</source>
<translation>Aucune mise à jour disponible</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="217"/>
<source>Error preparing download</source>
<translation>Erreur lors de la préparation du téléchargement</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="222"/>
<source>Download size is 0. Game already exists</source>
<translation>La taille du téléchargement est de 0. Le jeu existe déjà</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="229"/>
<source>Installing game: </source>
<translation>Installation du jeu: </translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="288"/>
<source>Installation finished</source>
<translation>Installation terminée</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="289"/>
<source>Finished Download of game {}</source>
<translation>Fin du téléchargement du jeu {}</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="312"/>
<source>Installing Game: No active download</source>
<translation>Installation du jeu: Aucun téléchargement actif</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="320"/>
<source>Download speed</source>
<translation>Vitesse de téléchargement</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="321"/>
<source>Cache used</source>
<translation>Cache utilisé</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="322"/>
<source>Downloaded</source>
<translation>Téléchargé</translation>
</message>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="323"/>
<source>Time left: </source>
<translation>Il reste du temps: </translation>
</message>
</context>
<context>
<name>DxvkWidget</name>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="18"/>
<source>GPU usage</source>
<translation>GPU Utilisation</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="19"/>
<source>Used Memory</source>
<translation>utilisé Memory</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="20"/>
<source>Device info</source>
<translation>Info sur le dispositif</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="21"/>
<source>DXVK version</source>
<translation>DXVK version</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="22"/>
<source>D3D Level of application</source>
<translation>D3D Niveau d'application</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="23"/>
<source>Frame time graph</source>
<translation>Graphique de temps de trame</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Dxvk.py" line="28"/>
<source>dxvk settings</source>
<translation>dxvk paramètres</translation>
</message>
</context>
<context>
<name>GameActions</name>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="182"/>
<source>Uninstall game</source>
<translation>Désinstaller le jeu</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="184"/>
<source>Uninstall</source>
<translation>Désinstaller</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="190"/>
<source>Verify Game</source>
<translation>Vérifier le jeu</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="195"/>
<source>Verify</source>
<translation>Vérifier</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="204"/>
<source>Repair Game</source>
<translation>Jeu de réparation</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="206"/>
<source>Repair</source>
<translation>Réparation</translation>
</message>
</context>
<context>
<name>GameInfo</name>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="98"/>
<source>Are you sure to uninstall {}</source>
<translation>Etes-vous sûr de désinstaller {}</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="106"/>
<source>Repair file does not exist or game does not need a repair. Please verify game first</source>
<translation>Le fichier de réparation n'existe pas ou le jeu ne nécessite pas de réparation. Veuillez d'abord vérifier le jeu.</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="131"/>
<source>Verification failed, {} file(s) corrupted, {} file(s) are missing. Do you want to repair them?</source>
<translation>La vérification a échoué, {} fichier(s) corrompu(s), {} fichier(s) manquant(s). Voulez-vous les réparer ?</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="160"/>
<source>Developer: </source>
<translation>Développeur: </translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="161"/>
<source>Install size: </source>
<translation>Taille d'installation: </translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="163"/>
<source>Install path: </source>
<translation>Chemin d'installation:</translation>
</message>
</context>
<context>
<name>GameList</name>
<message>
<location filename="../Components/Tabs/Games/GameList.py" line="45"/>
<source>Installed Games: {} Available Games: {}</source>
<translation>Jeux installés: {} Jeux disponibles: {}</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameList.py" line="150"/>
<source>Launch</source>
<translation>Lancer</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameList.py" line="155"/>
<source>Game running</source>
<translation>Jeu en cours</translation>
</message>
</context>
<context>
<name>GameListHeadBar</name>
<message>
<location filename="../Components/Tabs/Games/GamesTab.py" line="74"/>
<source>Installed only</source>
<translation>Installé uniquement</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GamesTab.py" line="81"/>
<source>Import Game</source>
<translation>Jeu d'importation</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GamesTab.py" line="93"/>
<source>Search Game</source>
<translation>Rechercher un jeu</translation>
</message>
</context>
<context>
<name>GameSettings</name>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="27"/>
<source>Launch Game offline</source>
<translation>Lancer le jeu hors ligne</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="32"/>
<source>Skip update check before launching</source>
<translation>Sauter la vérification de la mise à jour avant le lancement</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="65"/>
<source>Save</source>
<translation>Sauvez</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="41"/>
<source>Wrapper (e.g. optirun)</source>
<translation>Wrapper (p.e. optirun)</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="61"/>
<source>Proton Wrapper</source>
<translation>Enveloppeur de Proton</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="67"/>
<source>Proton prefix</source>
<translation>Préfixe du proton</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="173"/>
<source>No permission to create folder</source>
<translation>Pas de permission pour créer un dossier</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameSettings.py" line="218"/>
<source>Please select path for proton prefix</source>
<translation>Veuillez sélectionner le chemin pour le préfixe proton</translation>
</message>
</context>
<context>
<name>GameWidgetInstalled</name>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/InstalledIconWidget.py" line="36"/>
<source>Update available</source>
<translation>Mise à jour disponible</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/InstalledIconWidget.py" line="86"/>
<source>Start game without version check</source>
<translation>Démarrer le jeu sans vérifier la version</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/InstalledIconWidget.py" line="94"/>
<source>Game running</source>
<translation>Jeu en cours</translation>
</message>
</context>
<context>
<name>IconWidgetUninstalled</name>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/UninstalledIconWidget.py" line="43"/>
<source>Install Game</source>
<translation>Installer le jeu</translation>
</message>
</context>
<context>
<name>ImportWidget</name>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="99"/>
<source>Import</source>
<translation>Importer</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="39"/>
<source>Could not find EGL program data</source>
<translation>Impossible de trouver les données du programme EGL</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="42"/>
<source>Found EGL program Data. Do you want to import them?</source>
<translation>Les données du programme EGL ont é trouvées. Voulez-vous les importer ?</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="51"/>
<source>Could not find any Epic Games login data</source>
<translation>Impossible de trouver les données de connexion d'Epic Games</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="86"/>
<source>Loading...</source>
<translation>Chargement...</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/ImportWidget.py" line="98"/>
<source>Error: No valid session found</source>
<translation>Erreur : Aucune session valide n'a é trouvée</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="26"/>
<source>Back</source>
<translation>Dos</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="40"/>
<source>Select path to game</source>
<translation>Sélectionnez le chemin vers le jeu</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="43"/>
<source>Override app name (Only if imported game from legendary or the app could not find the app name)</source>
<translation>Remplacer le nom de l'application (uniquement si le jeu a été importé depuis le légendaire ou si l'application n'a pas pu trouver le nom de l'application)</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="62"/>
<source>Import Game</source>
<translation>Import jeu</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="70"/>
<source>Import all games from Epic Games Launcher</source>
<translation>Importer tous les jeux du Epic Games Launcher</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="110"/>
<source>Could not find app name</source>
<translation>Impossible de trouver le nom de l'application</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="114"/>
<source>Successfully imported {}. Reload library</source>
<translation>Importation réussie de {}. Recharger la bibliothèque</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="120"/>
<source>Failed to import {}</source>
<translation>Impossible d'importer {}</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/ImportWidget.py" line="159"/>
<source>Successfully imported {} Games. Reloading Library</source>
<translation>Importation réussie de {} Jeux. Bibliothèque de rechargement</translation>
</message>
</context>
<context>
<name>InfoTabs</name>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="26"/>
<source>Back</source>
<translation>Dos</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="30"/>
<source>Game Info</source>
<translation>Info de jeu</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameInfo/GameInfo.py" line="32"/>
<source>Settings</source>
<translation>Paramètres</translation>
</message>
</context>
<context>
<name>InstallDialog</name>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="20"/>
<source>&lt;h3&gt;Install {}&lt;/h3&gt;</source>
<translation>&lt;h3&gt;Installer {}&lt;/h3&gt;</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="29"/>
<source>Max workers (0: Default)</source>
<translation>Travailleurs maximum (0: Par défaut)</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="33"/>
<source>Force download</source>
<translation>Téléchargement forcé</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="37"/>
<source>Ignore free space (Warning!)</source>
<translation>Ignorer l'espace libre (Attention!)</translation>
</message>
</context>
<context>
<name>InstallInfoDialog</name>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="70"/>
<source>Download size: {}GB
Install size: {}GB</source>
<translation>Taille du téléchargement: {}GB
Taille de l'installation: {}GB</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="76"/>
<source>Install</source>
<translation>Installer</translation>
</message>
<message>
<location filename="../Components/Dialogs/InstallDialog.py" line="78"/>
<source>Cancel</source>
<translation>Annuler</translation>
</message>
</context>
<context>
<name>InstalledListWidget</name>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/InstalledListWidget.py" line="41"/>
<source>Launch</source>
<translation>Lancer</translation>
</message>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/InstalledListWidget.py" line="52"/>
<source>Developer: </source>
<translation>Développeur: </translation>
</message>
</context>
<context>
<name>LaunchDialog</name>
<message>
<location filename="../Components/Launch/LaunchDialog.py" line="60"/>
<source>Launching Rare</source>
<translation>Lancer Rare</translation>
</message>
<message>
<location filename="../Components/Launch/LaunchDialog.py" line="62"/>
<source>Logging in</source>
<translation>Se connecter</translation>
</message>
<message>
<location filename="../Components/Launch/LaunchDialog.py" line="80"/>
<source>Downloading Images</source>
<translation>Téléchargement d'images</translation>
</message>
<message>
<location filename="../Components/Launch/LaunchDialog.py" line="91"/>
<source>Starting...</source>
<translation>Démarrage...</translation>
</message>
</context>
<context>
<name>LaunchThread</name>
<message>
<location filename="../Components/Launch/LaunchDialog.py" line="23"/>
<source>Downloading Images</source>
<translation>Téléchargement d'images</translation>
</message>
</context>
<context>
<name>LegendarySettings</name>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="18"/>
<source>Legendary settings</source>
<translation>Legendary paramètres</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="27"/>
<source>Default installation directory</source>
<translation>Répertoire d'installation par défaut</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="36"/>
<source>Max workers for Download (Less: slower download)(0: Default)</source>
<translation>Nombre maximum de travailleurs pour le téléchargement (Moins: téléchargement plus lent)(0: Défaut)</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="43"/>
<source>Cleanup</source>
<translation>Nettoyage</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="44"/>
<source>Remove everything</source>
<translation>Enlever tout</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="48"/>
<source>Clean, but keep manifests</source>
<translation>Nettoyer, mais garder les manifestes</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Legendary.py" line="96"/>
<source>Cleanup complete! Successfully removed {} MB</source>
<translation>Nettoyage terminé ! J'ai réussi à supprimer {} MB</translation>
</message>
</context>
<context>
<name>LinuxSettings</name>
<message>
<location filename="../Components/Tabs/Settings/Linux.py" line="19"/>
<source>Linux settings</source>
<translation>Linux paramètres</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Linux.py" line="28"/>
<source>Default Wine Prefix</source>
<translation>Défaut Wine Prefix</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Linux.py" line="36"/>
<source>Default Wine executable</source>
<translation>Défaut Wine exécutable</translation>
</message>
</context>
<context>
<name>ListWidgetUninstalled</name>
<message>
<location filename="../Components/Tabs/Games/GameWidgets/UninstalledListWidget.py" line="29"/>
<source>Install</source>
<translation>Installer</translation>
</message>
</context>
<context>
<name>LoginDialog</name>
<message>
<location filename="../Components/Dialogs/Login/LoginDialog.py" line="30"/>
<source>Select one option to Login</source>
<translation>Sélectionnez une option pour vous connecter</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/LoginDialog.py" line="33"/>
<source>Use Browser</source>
<translation>Utiliser le navigateur</translation>
</message>
<message>
<location filename="../Components/Dialogs/Login/LoginDialog.py" line="33"/>
<source>This opens your default browser. Login and copy the text</source>
<translation>Cela ouvre votre navigateur par défaut. Connectez-vous et copiez le texte</translation>
</message>
</context>
<context>
<name>MiniWidget</name>
<message>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="20"/>
<source>Logged in as </source>
<translation>Connecté en tant que </translation>
</message>
<message>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="22"/>
<source>Account settings</source>
<translation>paramètres du compte</translation>
</message>
<message>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="26"/>
<source>Logout</source>
<translation>Déconnexion</translation>
</message>
<message>
<location filename="../Components/Tabs/Account/AccountWidget.py" line="32"/>
<source>Do you really want to logout</source>
<translation>Voulez-vous vraiment vous déconnecter?</translation>
</message>
</context>
<context>
<name>PathEdit</name>
<message>
<location filename="../utils/QtExtensions.py" line="133"/>
<source>Select Path</source>
<translation>Sélectionner le chemin</translation>
</message>
<message>
<location filename="../utils/QtExtensions.py" line="146"/>
<source>Choose Path</source>
<translation>Choisir le chemin</translation>
</message>
</context>
<context>
<name>PathInputDialog</name>
<message>
<location filename="../Components/Dialogs/PathInputDialog.py" line="24"/>
<source>Cancel</source>
<translation>Annuler</translation>
</message>
</context>
<context>
<name>RareSettings</name>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="19"/>
<source>Rare settings</source>
<translation>Rare paramètres</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="33"/>
<source>Save</source>
<translation>Sauvez</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="35"/>
<source>Image Directory</source>
<translation>Répertoire d'images</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="49"/>
<source>Language</source>
<translation>Langue</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="55"/>
<source>Confirm launch of game</source>
<translation>Confirmation du lancement du jeu</translation>
</message>
<message>
<location filename="../Components/Tabs/Settings/Rare.py" line="77"/>
<source>Restart Application to activate changes</source>
<translation>Redémarrez l'application pour activer les changements</translation>
</message>
</context>
<context>
<name>SyncSaves</name>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="65"/>
<source>Cloud Saves</source>
<translation>Cloud Saves</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="54"/>
<source>Found Saves for folowing Games</source>
<translation>Sauvegardes trouvées pour les jeux suivants</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="65"/>
<source>Your games does not support Cloud Saves</source>
<translation>Vos jeux ne prennent pas en charge les sauvegardes en nuage</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="70"/>
<source>Sync all games</source>
<translation>Sync tous les jeux</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="110"/>
<source>Found no savepath</source>
<translation>Pas de chemin de sauvegarde trouvé</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/CloudSaves.py" line="110"/>
<source>No save path was found. Please select path or skip</source>
<translation>Aucun chemin de sauvegarde n'a é trouvé. Veuillez sélectionner le chemin ou passer</translation>
</message>
</context>
<context>
<name>SyncWidget</name>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="60"/>
<source>Path not found</source>
<translation>Chemin non trouvé</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="76"/>
<source>Local Save date: </source>
<translation>Local Save date: </translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="79"/>
<source>No Local Save files</source>
<translation>Pas de fichiers de sauvegarde locaux</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="81"/>
<source>Cloud save date: </source>
<translation>Cloud save date: </translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="83"/>
<source>No Cloud saves</source>
<translation>Pas Cloud saves</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="87"/>
<source>Game is up to date</source>
<translation>Le jeu est à jour</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="88"/>
<source>Upload anyway</source>
<translation>Télécharger quand même</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="89"/>
<source>Download anyway</source>
<translation>Télécharger en tout cas</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="91"/>
<source>Cloud save is newer</source>
<translation>La sauvegarde en nuage est plus récente</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="92"/>
<source>Download Cloud saves</source>
<translation>Télécharger Cloud saves</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="96"/>
<source>Upload Saves</source>
<translation>Upload Saves</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="107"/>
<source>Local save is newer</source>
<translation>La sauvegarde locale est plus récente</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="108"/>
<source>Upload saves</source>
<translation>Upload Saves</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="112"/>
<source>Download saves</source>
<translation>Télécharger les sauvegardes</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="134"/>
<source>Change path</source>
<translation>Changement de trajectoire</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="158"/>
<source>Uploading...</source>
<translation>Téléchargement...</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="166"/>
<source>Upload finished</source>
<translation>Téléchargement terminé</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="176"/>
<source>Downloading...</source>
<translation>Téléchargement...</translation>
</message>
<message>
<location filename="../Components/Tabs/CloudSaves/SyncWidget.py" line="182"/>
<source>Download finished</source>
<translation>Téléchargement terminé</translation>
</message>
</context>
<context>
<name>TabWidget</name>
<message>
<location filename="../Components/TabWidget.py" line="23"/>
<source>Games</source>
<translation>Jeus</translation>
</message>
</context>
<context>
<name>UpdateWidget</name>
<message>
<location filename="../Components/Tabs/Downloads/DownloadTab.py" line="353"/>
<source>Update Game</source>
<translation>Jeu de mise à jour</translation>
</message>
</context>
</TS>

View file

@ -47,7 +47,9 @@ def launch_game(core, app_name: str, offline: bool = False, skip_version_check:
return process, params return process, params
def uninstall(app_name: str, core): def uninstall(app_name: str, core, options=None):
if not options:
options = {"keep_files": False}
igame = core.get_installed_game(app_name) igame = core.get_installed_game(app_name)
try: try:
# Remove DLC first so directory is empty when game uninstall runs # Remove DLC first so directory is empty when game uninstall runs
@ -55,12 +57,13 @@ def uninstall(app_name: str, core):
for dlc in dlcs: for dlc in dlcs:
if (idlc := core.get_installed_game(dlc.app_name)) is not None: if (idlc := core.get_installed_game(dlc.app_name)) is not None:
logger.info(f'Uninstalling DLC "{dlc.app_name}"...') logger.info(f'Uninstalling DLC "{dlc.app_name}"...')
core.uninstall_game(idlc, delete_files=True) 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=True, delete_root_directory=True) core.uninstall_game(igame, delete_files=not options["keep_files"], delete_root_directory=True)
logger.info('Game has been uninstalled.') logger.info('Game has been uninstalled.')
shutil.rmtree(igame.install_path) if not options["keep_files"]:
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.')

View file

@ -4,15 +4,15 @@ import shutil
from logging import getLogger from logging import getLogger
import requests import requests
from PIL import Image from PIL import Image, UnidentifiedImageError
from PyQt5.QtCore import pyqtSignal, QLocale, QSettings from PyQt5.QtCore import pyqtSignal, QLocale, QSettings
from Rare import lang_path from Rare import lang_path, __version__
from custom_legendary.core import LegendaryCore from custom_legendary.core import LegendaryCore
logger = getLogger("Utils") logger = getLogger("Utils")
s = QSettings("Rare", "Rare") s = QSettings("Rare", "Rare")
IMAGE_DIR = s.value("img_dir", os.path.expanduser("~/.cache/rare"), type=str) IMAGE_DIR = s.value("img_dir", os.path.expanduser("~/.cache/rare/images"), type=str)
logger.info("IMAGE DIRECTORY: " + IMAGE_DIR) logger.info("IMAGE DIRECTORY: " + IMAGE_DIR)
@ -38,6 +38,7 @@ def download_image(game, force=False):
if not os.path.isdir(f"{IMAGE_DIR}/" + game.app_name): if not os.path.isdir(f"{IMAGE_DIR}/" + game.app_name):
os.mkdir(f"{IMAGE_DIR}/" + game.app_name) os.mkdir(f"{IMAGE_DIR}/" + game.app_name)
# to git 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} json_data = {"DieselGameBoxTall": None, "DieselGameBoxLogo": None}
else: else:
@ -56,7 +57,13 @@ def download_image(game, force=False):
url = image["url"] url = image["url"]
with open(f"{IMAGE_DIR}/{game.app_name}/{image['type']}.png", "wb") as f: with open(f"{IMAGE_DIR}/{game.app_name}/{image['type']}.png", "wb") as f:
f.write(requests.get(url).content) f.write(requests.get(url).content)
f.close() try:
img = Image.open(f"{IMAGE_DIR}/{game.app_name}/{image['type']}.png")
img = img.resize((200, int(200*4/3)))
img.save(f"{IMAGE_DIR}/{game.app_name}/{image['type']}.png")
except UnidentifiedImageError as e:
logger.warning(e)
# scale and grey # scale and grey
if not os.path.isfile(f'{IMAGE_DIR}/' + game.app_name + '/UninstalledArt.png'): if not os.path.isfile(f'{IMAGE_DIR}/' + game.app_name + '/UninstalledArt.png'):
@ -67,6 +74,7 @@ def download_image(game, force=False):
bg = Image.open(f"{IMAGE_DIR}/{game.app_name}/DieselGameBoxTall.png") bg = Image.open(f"{IMAGE_DIR}/{game.app_name}/DieselGameBoxTall.png")
uninstalledArt = bg.convert('L') uninstalledArt = bg.convert('L')
uninstalledArt = uninstalledArt.resize((200, int(200*4/3)))
uninstalledArt.save(f'{IMAGE_DIR}/{game.app_name}/UninstalledArt.png') uninstalledArt.save(f'{IMAGE_DIR}/{game.app_name}/UninstalledArt.png')
elif os.path.isfile(f"{IMAGE_DIR}/{game.app_name}/DieselGameBoxLogo.png"): elif os.path.isfile(f"{IMAGE_DIR}/{game.app_name}/DieselGameBoxLogo.png"):
bg: Image.Image = Image.open(f"{IMAGE_DIR}/{game.app_name}/DieselGameBoxLogo.png") bg: Image.Image = Image.open(f"{IMAGE_DIR}/{game.app_name}/DieselGameBoxLogo.png")
@ -91,7 +99,7 @@ def download_image(game, force=False):
uninstalledArt = uninstalledArt.convert('L') uninstalledArt = uninstalledArt.convert('L')
uninstalledArt.save(f'{IMAGE_DIR}/' + game.app_name + '/UninstalledArt.png') uninstalledArt.save(f'{IMAGE_DIR}/' + game.app_name + '/UninstalledArt.png')
else: else:
logger.warning(f"File {IMAGE_DIR}/{game.app_name}/DieselGameBoxTall.png dowsn't exist") logger.warning(f"File {IMAGE_DIR}/{game.app_name}/DieselGameBoxTall.png doesn't exist")
def get_lang(): def get_lang():
@ -110,3 +118,9 @@ def get_possible_langs():
if i.endswith(".qm"): if i.endswith(".qm"):
langs.append(i.split(".")[0]) langs.append(i.split(".")[0])
return langs return langs
def get_latest_version():
resp = requests.get("https://api.github.com/repos/Dummerle/Rare/releases/latest")
tag = json.loads(resp.content.decode("utf-8"))["tag_name"]
return tag