diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2aaf6c60..643f779e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -46,7 +46,7 @@ jobs:
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
commit_message: Update AUR package
- pyinstaller-windows:
+ cx-freeze-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
@@ -55,25 +55,18 @@ jobs:
python-version: '3.8'
- name: Install python deps
run: |
- pip3 install pyinstaller setuptools wheel
+ pip3 install cx_Freeze setuptools wheel
pip3 install -r requirements.txt
- - name: Prepare
- run: cp rare/__main__.py ./
- name: Build
- run: pyinstaller
- --icon=rare/styles/Logo.ico
- --onefile
- --name Rare
- --add-data="Rare/languages/*;Rare/languages"
- --add-data="Rare/Styles/*;Rare/Styles"
- --windowed
- __main__.py
+ run: python3 freeze.py bdist_msi
+ - name: Copy File
+ run: cp dist/*.msi Rare.msi
- name: Upload files to GitHub
uses: svenstaro/upload-release-action@2.2.1
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
- file: dist/Rare.exe
- asset_name: Rare.exe
+ file: Rare.msi
+ asset_name: Rare.msi
tag: ${{ github.ref }}
overwrite: true
diff --git a/.gitignore b/.gitignore
index 2547711d..af20f06c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,5 +6,7 @@ __pycache__
/.vscode
/build
/dist
+/deb_dist
+*.tar.gz
/Rare.egg-info/
-/venv
\ No newline at end of file
+/venv
diff --git a/README.md b/README.md
index 6d31d84f..ff0486ab 100644
--- a/README.md
+++ b/README.md
@@ -41,12 +41,14 @@ Install via `pip`.
## Run from source
1. Run `pip install -r requirements.txt` to get dependencies. If you use `pacman` you can run `sudo pacman --needed -S python-wheel python-setuptools python-pyqt5 python-qtawesome python-requests python-pillow`
-2. For unix operating systems run `sh start.sh`. For windows run `set PYTHONPATH=%CD% && python Rare`
+2. For unix operating systems run `sh start.sh`. For windows run `set PYTHONPATH=%CD% && python rare`
## Why Rare?
-- Rare uses much less RAM than electron based apps such as [HeroicGL](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher) and EpicGL which allows the games to run better.
-- Rare supports all major platforms (Windows, Linux, macOS) unlike the alternatives.
+- Rare only uses ~50MB of RAM which is much less than the electron based [HeroicGamesLauncher](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher) uses.
+- Rare supports all major platforms (Windows, Linux, Mac) unlike the alternatives.
+
+**Note** Mac should work too, but I have no Mac and I can't test it.
## Features
@@ -60,7 +62,7 @@ Install via `pip`.
## Planned Features
- More Translations (Need help)
- More Information about Games
-More planned features are in projects
+More planned features are in [projects](https://github.com/Dummerle/Rare/projects/1)
## Contributing
There are more options to contribute.
diff --git a/freeze.py b/freeze.py
new file mode 100644
index 00000000..10fe3052
--- /dev/null
+++ b/freeze.py
@@ -0,0 +1,74 @@
+import sys
+
+from cx_Freeze import setup, Executable
+
+from rare import __version__
+
+# Packages to include
+python_packages = []
+
+# Modules to include
+python_modules = []
+
+base = None
+name = None
+build_options = {}
+build_exe_options = {}
+shortcutName = None
+shortcutDir = None
+bdist_msi_options = None
+src_files = []
+external_so_files = []
+
+if sys.platform == 'win32':
+ base = 'Win32GUI'
+ name = 'Rare.exe'
+ shortcut_table = [
+ ('DesktopShortcut', # Shortcut
+ 'DesktopFolder', # Directory
+ 'Rare', # Name
+ 'TARGETDIR', # Component
+ '[TARGETDIR]'+name, # Target
+ None, # Arguments
+ 'A gui for Legendary.', # Description
+ None, # Hotkey
+ None, # Icon
+ None, # IconIndex
+ None, # ShowCmd
+ 'TARGETDIR' # Working Directory
+ )]
+ msi_data = {"Shortcut": shortcut_table}
+ bdist_msi_options = {'data': msi_data, "all_users": True}
+ build_options["bdist_msi"] = bdist_msi_options
+else:
+ name = 'Rare'
+
+src_files += [
+ 'LICENSE',
+ 'README.md',
+ 'rare/styles/Logo.ico',
+]
+
+# Dependencies are automatically detected, but it might need fine tuning.
+build_exe_options["packages"] = python_packages
+build_exe_options["include_files"] = src_files + external_so_files
+build_exe_options["includes"] = python_modules
+build_exe_options["excludes"] = ["setuptools", "tkinter", "pkg_resources"]
+
+# Set options
+build_options["build_exe"] = build_exe_options
+
+setup(name = 'Rare',
+ version = __version__,
+ description = 'A gui for Legendary.',
+ options = build_options,
+ executables = [
+ Executable('rare/__main__.py',
+ targetName=name,
+ icon='rare/styles/Logo.ico',
+ base=base,
+ shortcutName=shortcutName,
+ shortcutDir=shortcutDir,
+ ),
+ ],
+)
diff --git a/rare/__init__.py b/rare/__init__.py
index 4a55d0b2..e826caf3 100644
--- a/rare/__init__.py
+++ b/rare/__init__.py
@@ -1,5 +1,5 @@
import os
-__version__ = "1.3.0"
+__version__ = "1.4.1"
style_path = os.path.join(os.path.dirname(__file__), "styles/")
lang_path = os.path.join(os.path.dirname(__file__), "languages/")
diff --git a/rare/app.py b/rare/app.py
index 5acebeb7..7863bd8d 100644
--- a/rare/app.py
+++ b/rare/app.py
@@ -1,7 +1,6 @@
import configparser
import logging
import os
-import shutil
import sys
import time
@@ -16,7 +15,7 @@ from rare.components.main_window import MainWindow
from rare.components.tray_icon import TrayIcon
from rare.utils.utils import get_lang, load_color_scheme
-start_time = time.strftime('%y-%m-%d--%H:%M') # year-month-day-hour-minute
+start_time = time.strftime('%y-%m-%d--%H-%M') # year-month-day-hour-minute
file_name = os.path.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))
diff --git a/rare/components/tab_widget.py b/rare/components/tab_widget.py
index a7d11f21..39e51a83 100644
--- a/rare/components/tab_widget.py
+++ b/rare/components/tab_widget.py
@@ -3,9 +3,14 @@ import webbrowser
from PyQt5.QtCore import QSize, pyqtSignal
from PyQt5.QtWidgets import QMenu, QTabWidget, QWidget, QWidgetAction
from qtawesome import icon
+from rare.utils import legendary_utils
from custom_legendary.core import LegendaryCore
+
from rare.components.dialogs.install_dialog import InstallDialog
+
+from rare.components.dialogs.uninstall_dialog import UninstallDialog
+
from rare.components.tab_utils import TabBar, TabButtonWidget
from rare.components.tabs.account import MiniWidget
from rare.components.tabs.cloud_saves import SyncSaves
@@ -64,6 +69,12 @@ class TabWidget(QTabWidget):
# open download tab
self.games_tab.default_widget.game_list.update_game.connect(lambda: self.setCurrentIndex(1))
+ # uninstall
+ self.games_tab.game_info.info.uninstall_game.connect(self.uninstall_game)
+
+ # imported
+ self.games_tab.import_widget.update_list.connect(self.game_imported)
+
if not offline:
# Download finished
self.downloadTab.finished.connect(self.dl_finished)
@@ -87,6 +98,7 @@ class TabWidget(QTabWidget):
self.tabBarClicked.connect(lambda x: self.games_tab.layout.setCurrentIndex(0) if x == 0 else None)
self.setIconSize(QSize(25, 25))
+
def install_game(self, app_name, disable_path=False):
infos = InstallDialog(app_name, self.core, disable_path).get_information()
@@ -102,16 +114,38 @@ class TabWidget(QTabWidget):
self.setTabText(1, "Downloads" + ((" (" + str(downloads) + ")") if downloads != 0 else ""))
self.downloadTab.install_game(options)
+ def game_imported(self, app_name: str):
+ igame = self.core.get_installed_game(app_name)
+ if self.core.get_asset(app_name, True).build_version != igame.version:
+ self.downloadTab.add_update(igame)
+ downloads = len(self.downloadTab.dl_queue) + len(self.downloadTab.update_widgets.keys())
+ self.setTabText(1, "Downloads" + ((" (" + str(downloads) + ")") if downloads != 0 else ""))
+ self.games_tab.default_widget.game_list.update_list(app_name)
+ self.games_tab.layout.setCurrentIndex(0)
+
# Sync game and delete dc rpc
def game_finished(self, app_name):
self.delete_presence.emit()
if self.core.get_game(app_name).supports_cloud_saves:
self.cloud_saves.sync_game(app_name, True)
+ def uninstall_game(self, app_name):
+ game = self.core.get_game(app_name)
+ infos = UninstallDialog(game).get_information()
+ if infos == 0:
+ return
+ legendary_utils.uninstall(game.app_name, self.core, infos)
+ if app_name in self.downloadTab.update_widgets.keys():
+ self.downloadTab.update_layout.removeWidget(self.downloadTab.update_widgets[app_name])
+ self.downloadTab.update_widgets.pop(app_name)
+ downloads = len(self.downloadTab.dl_queue) + len(self.downloadTab.update_widgets.keys())
+ self.setTabText(1, "Downloads" + ((" (" + str(downloads) + ")") if downloads != 0 else ""))
+ self.downloadTab.update_text.setVisible(len(self.downloadTab.update_widgets) == 0)
# Update gamelist and set text of Downlaods to "Downloads"
+
def dl_finished(self, update_list):
- if update_list:
- self.games_tab.default_widget.game_list.update_list()
+ if update_list[0]:
+ self.games_tab.default_widget.game_list.update_list(update_list[1])
downloads = len(self.downloadTab.dl_queue) + len(self.downloadTab.update_widgets.keys())
self.setTabText(1, "Downloads" + ((" (" + str(downloads) + ")") if downloads != 0 else ""))
diff --git a/rare/components/tabs/cloud_saves/sync_widget.py b/rare/components/tabs/cloud_saves/sync_widget.py
index 46ccdd5a..889a7ee3 100644
--- a/rare/components/tabs/cloud_saves/sync_widget.py
+++ b/rare/components/tabs/cloud_saves/sync_widget.py
@@ -1,7 +1,7 @@
import os
from logging import getLogger
-from PyQt5.QtCore import QThread, pyqtSignal, Qt
+from PyQt5.QtCore import QThread, pyqtSignal, Qt, QSettings
from PyQt5.QtWidgets import QVBoxLayout, QPushButton, QHBoxLayout, QLabel, QGroupBox
from custom_legendary.core import LegendaryCore
@@ -167,6 +167,11 @@ class SyncWidget(QGroupBox):
self.layout.addStretch(1)
self.setLayout(self.layout)
+ if self.res == SaveGameStatus.REMOTE_NEWER:
+ settings = QSettings()
+ if settings.value(f"{igame.app_name}/auto_sync_cloud", False, bool):
+ self.download()
+
def change_path(self):
path = PathInputDialog("Select directory", "Select savepath. Warning: Do not change if you are not sure",
self.igame.save_path).get_path()
diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py
index 47c29fe9..1cbb3ebe 100644
--- a/rare/components/tabs/downloads/__init__.py
+++ b/rare/components/tabs/downloads/__init__.py
@@ -20,7 +20,7 @@ logger = getLogger("Download")
class DownloadTab(QWidget):
- finished = pyqtSignal(bool)
+ finished = pyqtSignal(tuple)
thread: QThread
dl_queue = []
@@ -75,18 +75,22 @@ class DownloadTab(QWidget):
self.update_text.setVisible(len(updates) == 0)
for igame in updates:
- widget = UpdateWidget(core, igame, self)
- self.update_layout.addWidget(widget)
- self.update_widgets[igame.app_name] = widget
- widget.update_signal.connect(self.update_game)
- if QSettings().value("auto_update", False, bool):
- self.update_game(igame.app_name, True)
- widget.update_button.setDisabled(True)
+
+ self.add_update(igame)
self.layout.addStretch(1)
self.setLayout(self.layout)
+ def add_update(self, igame: InstalledGame):
+ widget = UpdateWidget(self.core, igame, self)
+ self.update_layout.addWidget(widget)
+ self.update_widgets[igame.app_name] = widget
+ widget.update.connect(self.update_game)
+ if QSettings().value("auto_update", False, bool):
+ self.update_game(igame.app_name, True)
+ widget.update_button.setDisabled(True)
+
def update_dl_queue(self, dl_queue):
self.dl_queue = dl_queue
@@ -135,7 +139,7 @@ class DownloadTab(QWidget):
# Information
if not from_update:
if not InstallInfoDialog(dl_size=analysis.dl_size, install_size=analysis.install_size).get_accept():
- self.finished.emit(False)
+ self.finished.emit(False, None)
return
if self.active_game is None:
@@ -213,23 +217,25 @@ class DownloadTab(QWidget):
# QMessageBox.information(self, "Info", "Download finished")
logger.info("Download finished: " + self.active_game.app_title)
+ app_name = self.active_game.app_name
+ self.active_game = None
+
if self.dl_queue:
- if self.dl_queue[0][1] == self.active_game.app_name:
+ if self.dl_queue[0][1] == 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():
- self.update_widgets[self.active_game.app_name].setVisible(False)
- self.update_widgets.pop(self.active_game.app_name)
+ if app_name in self.update_widgets.keys():
+ self.update_widgets[app_name].setVisible(False)
+ self.update_widgets.pop(app_name)
if len(self.update_widgets) == 0:
self.update_text.setVisible(True)
- self.active_game = None
-
for i in self.update_widgets.values():
i.update_button.setDisabled(False)
- self.finished.emit(True)
+ self.finished.emit((True, app_name))
+
self.reset_infos()
if len(self.dl_queue) != 0:
@@ -243,7 +249,7 @@ class DownloadTab(QWidget):
elif text == "stop":
self.reset_infos()
self.active_game = None
- self.finished.emit(False)
+ self.finished.emit((False, None))
if self.dl_queue:
self.start_installation(*self.dl_queue[0])
@@ -302,6 +308,7 @@ class UpdateWidget(QWidget):
self.update_with_settings.clicked.connect(lambda: self.update_game(False))
self.layout.addWidget(self.update_button)
self.layout.addWidget(self.update_with_settings)
+ self.layout.addWidget(QLabel(self.tr("Version: ") + self.game.version + " -> " + self.core.get_asset(self.game.app_name, True).build_version))
self.setLayout(self.layout)
diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py
index e425672a..7224f8ae 100644
--- a/rare/components/tabs/games/__init__.py
+++ b/rare/components/tabs/games/__init__.py
@@ -1,5 +1,6 @@
-from PyQt5.QtCore import QSettings, QSize
-from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QCheckBox, QLineEdit, QPushButton, QStackedLayout, QLabel
+from PyQt5.QtCore import QSettings, QSize, pyqtSignal
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLineEdit, QPushButton, QStackedLayout, \
+ QLabel, QComboBox
from qtawesome import icon
from rare.components.tabs.games.game_info import InfoTabs
@@ -36,8 +37,8 @@ class GameTab(QWidget):
self.setLayout(self.layout)
- def update_list(self):
- self.default_widget.game_list.update_list(self.default_widget.head_bar.view.isChecked())
+ def update_list(self, app_name=None):
+ self.default_widget.game_list.update_list(app_name)
self.layout.setCurrentIndex(0)
def show_uninstalled(self, app_name):
@@ -62,11 +63,10 @@ class Games(QWidget):
self.game_list = GameList(core, self, offline)
self.head_bar.search_bar.textChanged.connect(
- lambda: self.game_list.filter(self.head_bar.search_bar.text()))
+ lambda: self.game_list.search(self.head_bar.search_bar.text()))
+
+ self.head_bar.filter_changed_signal.connect(self.game_list.filter)
- 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)
@@ -81,14 +81,23 @@ class Games(QWidget):
class GameListHeadBar(QWidget):
+ filter_changed_signal = pyqtSignal(str)
+
def __init__(self, parent):
super(GameListHeadBar, self).__init__(parent=parent)
self.layout = QHBoxLayout()
- self.installed_only = QCheckBox(self.tr("Installed only"))
+ # 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.installed_only.setChecked(self.settings.value("installed_only", False, bool))
+ # self.layout.addWidget(self.installed_only)
+ self.filter = QComboBox()
+ self.filter.addItems([self.tr("All"),
+ self.tr("Installed only"),
+ self.tr("Offline Games"),
+ self.tr("32 Bit Games")])
+ self.filter.currentIndexChanged.connect(self.filter_changed)
+ self.layout.addWidget(self.filter)
self.layout.addStretch(1)
self.import_game = QPushButton(icon("mdi.import", color="white"), self.tr("Import Game"))
@@ -118,3 +127,6 @@ class GameListHeadBar(QWidget):
self.layout.addWidget(self.refresh_list)
self.setLayout(self.layout)
+
+ def filter_changed(self, i):
+ self.filter_changed_signal.emit(["", "installed", "offline", "32bit"][i])
diff --git a/rare/components/tabs/games/game_info/__init__.py b/rare/components/tabs/games/game_info/__init__.py
index e658e1c0..790d5dfb 100644
--- a/rare/components/tabs/games/game_info/__init__.py
+++ b/rare/components/tabs/games/game_info/__init__.py
@@ -59,7 +59,8 @@ class InfoTabs(QTabWidget):
class GameInfo(QWidget, Ui_GameInfo):
igame: InstalledGame
game: Game
- update_list = pyqtSignal()
+ uninstall_game = pyqtSignal(str)
+ update_list = pyqtSignal(str)
verify_game = pyqtSignal(str)
verify_threads = {}
@@ -87,12 +88,8 @@ class GameInfo(QWidget, Ui_GameInfo):
self.repair_button.clicked.connect(self.repair)
def uninstall(self):
- infos = UninstallDialog(self.game).get_information()
- if infos == 0:
- print("Cancel Uninstall")
- return
- legendary_utils.uninstall(self.game.app_name, self.core, infos)
- self.update_list.emit()
+ self.uninstall_game.emit(self.game.app_name)
+ self.update_list.emit(self.game.app_name)
def repair(self):
repair_file = os.path.join(self.core.lgd.get_tmp_path(), f'{self.game.app_name}.repair')
diff --git a/rare/components/tabs/games/game_list.py b/rare/components/tabs/games/game_list.py
index 8627b42f..771beeb9 100644
--- a/rare/components/tabs/games/game_list.py
+++ b/rare/components/tabs/games/game_list.py
@@ -4,9 +4,10 @@ from logging import getLogger
import psutil
from PyQt5.QtCore import Qt, pyqtSignal, QSettings, QTimer
from PyQt5.QtGui import QPixmap
-from PyQt5.QtWidgets import *
+from PyQt5.QtWidgets import QScrollArea, QWidget, QLabel, QVBoxLayout, QStackedWidget
from custom_legendary.core import LegendaryCore
+from rare.components.tabs.games.game_widgets.base_installed_widget import BaseInstalledWidget
from rare.components.tabs.games.game_widgets.installed_icon_widget import GameWidgetInstalled
from rare.components.tabs.games.game_widgets.installed_list_widget import InstalledListWidget
from rare.components.tabs.games.game_widgets.uninstalled_icon_widget import IconWidgetUninstalled
@@ -63,93 +64,36 @@ class GameList(QStackedWidget):
self.list_layout = QVBoxLayout()
self.list_layout.addWidget(QLabel(self.info_text))
- IMAGE_DIR = self.settings.value("img_dir", os.path.expanduser("~/.cache/rare/images"), str)
+ self.IMAGE_DIR = self.settings.value("img_dir", os.path.expanduser("~/.cache/rare/images"), str)
self.updates = []
self.widgets = {}
+ self.bit32 = [i.app_name for i in self.core.get_game_and_dlc_list(True, "Win32")[0]]
+ self.mac_games = [i.app_name for i in self.core.get_game_and_dlc_list(True, "Mac")[0]]
self.installed = sorted(self.core.get_installed_list(), key=lambda x: x.title)
# Installed Games
for igame in self.installed:
- if os.path.exists(f"{IMAGE_DIR}/{igame.app_name}/FinalArt.png"):
- pixmap = QPixmap(f"{IMAGE_DIR}/{igame.app_name}/FinalArt.png")
- elif os.path.exists(f"{IMAGE_DIR}/{igame.app_name}/DieselGameBoxTall.png"):
- pixmap = QPixmap(f"{IMAGE_DIR}/{igame.app_name}/DieselGameBoxTall.png")
- elif os.path.exists(f"{IMAGE_DIR}/{igame.app_name}/DieselGameBoxLogo.png"):
- pixmap = QPixmap(f"{IMAGE_DIR}/{igame.app_name}/DieselGameBoxLogo.png")
- else:
- logger.warning(f"No Image found: {igame.title}")
- pixmap = None
-
- if pixmap.isNull():
- logger.info(igame.title + " has a corrupt image.")
- download_image(igame, force=True)
- pixmap = QPixmap(f"{IMAGE_DIR}/{igame.app_name}/DieselGameBoxTall.png")
-
- icon_widget = GameWidgetInstalled(igame, self.core, pixmap, self.offline)
+ icon_widget, list_widget = self.add_installed_widget(igame)
self.icon_layout.addWidget(icon_widget)
-
- list_widget = InstalledListWidget(igame, self.core, pixmap, self.offline)
self.list_layout.addWidget(list_widget)
- icon_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.finished)
- icon_widget.update_list.connect(lambda: self.update_list(self.settings.value("icon_view", True, bool)))
- list_widget.launch_signal.connect(self.launch)
- list_widget.finish_signal.connect(self.finished)
- list_widget.update_list.connect(lambda: self.update_list(self.settings.value("icon_view", True, bool)))
-
- if icon_widget.update_available:
- self.updates.append(igame)
-
- self.widgets[igame.app_name] = (icon_widget, list_widget)
- active, pid = self.check_for_active_game(igame)
- if active:
- # Only one game works: Workaround for Satisfactory EA and Exp.
- self.launch(igame.app_name)
- icon_widget.game_running = True
- list_widget.game_running = True
- self.active_game = (igame.app_name, pid)
- self.timer = QTimer()
- self.timer.timeout.connect(self.is_finished)
- self.timer.start(10000)
-
- self.uninstalled_games = []
+ self.uninstalled_names = []
installed = [i.app_name for i in self.core.get_installed_list()]
# get Uninstalled games
games, self.dlcs = self.core.get_game_and_dlc_list()
for game in sorted(games, key=lambda x: x.app_title):
if not game.app_name in installed:
- self.uninstalled_games.append(game)
+ self.uninstalled_names.append(game)
# add uninstalled games
- for igame in self.uninstalled_games:
- if os.path.exists(f"{IMAGE_DIR}/{igame.app_name}/UninstalledArt.png"):
- pixmap = QPixmap(f"{IMAGE_DIR}/{igame.app_name}/UninstalledArt.png")
- if pixmap.isNull():
- logger.info(igame.app_title + " has a corrupt image.")
- download_image(igame, force=True)
- pixmap = QPixmap(f"{IMAGE_DIR}/{igame.app_name}/UninstalledArt.png")
- else:
- logger.warning(f"No Image found: {igame.app_title}")
- download_image(igame, force=True)
- pixmap = QPixmap(f"{IMAGE_DIR}/{igame.app_name}/UninstalledArt.png")
-
- icon_widget = IconWidgetUninstalled(igame, self.core, pixmap)
- icon_widget.show_uninstalled_info.connect(self.install)
-
- list_widget = ListWidgetUninstalled(self.core, igame, pixmap)
- list_widget.show_uninstalled_info.connect(self.install)
+ for game in self.uninstalled_games:
+ icon_widget, list_widget = self.add_uninstalled_widget(game)
self.icon_layout.addWidget(icon_widget)
self.list_layout.addWidget(list_widget)
- self.widgets[igame.app_name] = (icon_widget, list_widget)
-
self.icon_parent_layout.addLayout(self.icon_layout)
self.icon_parent_layout.addStretch(1)
self.list_layout.addStretch(1)
@@ -165,8 +109,81 @@ class GameList(QStackedWidget):
if not icon_view:
self.setCurrentIndex(1)
- if self.settings.value("installed_only", False, bool):
- self.installed_only(True)
+ if filter_games := self.settings.value("filter", "", str):
+ self.filter(filter_games)
+
+ def add_uninstalled_widget(self, game):
+ if os.path.exists(f"{self.IMAGE_DIR}/{game.app_name}/UninstalledArt.png"):
+ pixmap = QPixmap(f"{self.IMAGE_DIR}/{game.app_name}/UninstalledArt.png")
+
+ if pixmap.isNull():
+ logger.info(game.app_title + " has a corrupt image.")
+ download_image(game, force=True)
+ pixmap = QPixmap(f"{self.IMAGE_DIR}/{game.app_name}/UninstalledArt.png")
+ else:
+ logger.warning(f"No Image found: {game.app_title}")
+ download_image(game, force=True)
+ pixmap = QPixmap(f"{self.IMAGE_DIR}/{game.app_name}/UninstalledArt.png")
+
+ icon_widget = IconWidgetUninstalled(game, self.core, pixmap)
+ icon_widget.install_game.connect(self.install)
+
+ list_widget = ListWidgetUninstalled(self.core, game, pixmap)
+ list_widget.install_game.connect(self.install)
+
+ self.widgets[game.app_name] = (icon_widget, list_widget)
+
+ return icon_widget, list_widget
+
+ def add_installed_widget(self, igame):
+ if os.path.exists(f"{self.IMAGE_DIR}/{igame.app_name}/FinalArt.png"):
+ pixmap = QPixmap(f"{self.IMAGE_DIR}/{igame.app_name}/FinalArt.png")
+ elif os.path.exists(f"{self.IMAGE_DIR}/{igame.app_name}/DieselGameBoxTall.png"):
+ pixmap = QPixmap(f"{self.IMAGE_DIR}/{igame.app_name}/DieselGameBoxTall.png")
+ elif os.path.exists(f"{self.IMAGE_DIR}/{igame.app_name}/DieselGameBoxLogo.png"):
+ pixmap = QPixmap(f"{self.IMAGE_DIR}/{igame.app_name}/DieselGameBoxLogo.png")
+ else:
+ logger.warning(f"No Image found: {igame.title}")
+ pixmap = None
+
+ if pixmap.isNull():
+ logger.info(igame.title + " has a corrupt image.")
+ download_image(igame, force=True)
+ pixmap = QPixmap(f"{self.IMAGE_DIR}/{igame.app_name}/DieselGameBoxTall.png")
+
+ icon_widget = GameWidgetInstalled(igame, self.core, pixmap, self.offline)
+ # self.icon_layout.addWidget(icon_widget)
+
+ list_widget = InstalledListWidget(igame, self.core, pixmap, self.offline)
+ # self.list_layout.addWidget(list_widget)
+
+ self.widgets[igame.app_name] = (icon_widget, list_widget)
+
+ icon_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.finished)
+ icon_widget.update_list.connect(self.update_list)
+ list_widget.launch_signal.connect(self.launch)
+ list_widget.finish_signal.connect(self.finished)
+ list_widget.update_list.connect(self.update_list)
+
+ if icon_widget.update_available:
+ self.updates.append(igame)
+
+ active, pid = self.check_for_active_game(igame)
+ if active:
+ # Only one game works: Workaround for Satisfactory EA and Exp.
+ self.launch(igame.app_name)
+ icon_widget.game_running = True
+ list_widget.game_running = True
+ self.active_game = (igame.app_name, pid)
+ self.timer = QTimer()
+ self.timer.timeout.connect(self.is_finished)
+ self.timer.start(10000)
+
+ return icon_widget, list_widget
def is_finished(self):
if psutil.pid_exists(self.active_game[1]):
@@ -226,7 +243,7 @@ class GameList(QStackedWidget):
self.widgets[app_name][1].launch_button.setDisabled(True)
self.widgets[app_name][1].launch_button.setText(self.tr("Game running"))
- def filter(self, text: str):
+ def search(self, text: str):
for t in self.widgets.values():
for w in t:
if text.lower() in w.game.app_title.lower() + w.game.app_name.lower():
@@ -234,31 +251,151 @@ class GameList(QStackedWidget):
else:
w.setVisible(False)
- def installed_only(self, i_o: bool):
+ def filter(self, filter="installed"):
for t in self.widgets.values():
for w in t:
- w.setVisible(not (not self.core.is_installed(w.game.app_name) and i_o))
- self.settings.setValue("installed_only", i_o)
+ if filter == "installed":
+ w.setVisible(self.core.is_installed(w.game.app_name))
+ elif filter == "offline":
+ if self.core.is_installed(w.game.app_name):
+ w.setVisible(w.igame.can_run_offline)
+ else:
+ w.setVisible(False)
+ elif filter == "32bit":
+ w.setVisible(w.game.app_name in self.bit32)
+ elif filter == "mac":
+ w.setVisible(w.game.app_name in self.mac_games)
+ else:
+ # All visible
+ w.setVisible(True)
+ self.settings.setValue("filter", filter)
- def update_list(self, icon_view=True):
- self.settings.setValue("icon_view", icon_view)
+ def update_list(self, app_name=None):
+ # self.settings.setValue("icon_view", icon_view)
+ if app_name:
+ if widgets := self.widgets.get(app_name):
- uninstalled_games = []
- installed = [i.app_name for i in self.core.get_installed_list()]
- # get Uninstalled games
- games = self.core.get_game_list(True)
- for game in sorted(games, key=lambda x: x.app_title):
- if not game.app_name in installed:
- uninstalled_games.append(game.app_name)
+ # from update
+ if self.core.is_installed(widgets[0].game.app_name) and isinstance(widgets[0], BaseInstalledWidget):
+ igame = self.core.get_installed_game(app_name)
+ for w in widgets:
+ w.igame = igame
+ w.update_available = self.core.get_asset(w.game.app_name, True).build_version != igame.version
+ widgets[0].info_label.setText("")
+ widgets[0].info_text = ""
+ # new installed
+ elif self.core.is_installed(widgets[0].game.app_name) and not isinstance(widgets[0], BaseInstalledWidget):
+ self.widgets.pop(widgets[0].game.app_name)
- # get new uninstalled/ installed games that changed
- new_installed_games = list(set(installed) - set([i.app_name for i in self.installed]))
- new_uninstalled_games = list(set(uninstalled_games) - set([i.app_name for i in self.uninstalled_games]))
+ # QWidget().setLayout(self.icon_layout)
+ icon_layout = FlowLayout()
+
+ # QWidget().setLayout(self.list_layout)
+ list_layout = QVBoxLayout()
+
+ igame = self.core.get_installed_game(app_name)
+ self.add_installed_widget(igame)
+
+ for igame in sorted(self.core.get_installed_list(), key=lambda x: x.title):
+ i_widget, l_widget = self.widgets[igame.app_name]
+ icon_layout.addWidget(i_widget)
+ list_layout.addWidget(l_widget)
+
+ self.uninstalled_names = []
+ installed_names = [i.app_name for i in self.core.get_installed_list()]
+ # get Uninstalled games
+ games, self.dlcs = self.core.get_game_and_dlc_list()
+ for game in sorted(games, key=lambda x: x.app_title):
+ if not game.app_name in installed_names:
+ self.uninstalled_names.append(game)
+ for game in self.uninstalled_names:
+ i_widget, list_widget = self.widgets[game.app_name]
+ icon_layout.addWidget(i_widget)
+ list_layout.addWidget(list_widget)
+
+ QWidget().setLayout(self.icon_layout)
+ QWidget().setLayout(self.list_layout)
+
+ self.icon_widget = QWidget()
+ self.list_widget = QWidget()
+
+ self.icon_widget.setLayout(icon_layout)
+ self.list_widget.setLayout(list_layout)
+
+ self.list_scrollarea.setWidget(QWidget())
+ self.icon_scrollarea.setWidget(QWidget())
+
+ self.icon_scrollarea.setWidget(self.icon_widget)
+ self.list_scrollarea.setWidget(self.list_widget)
+
+ self.icon_layout = icon_layout
+ self.list_layout = list_layout
+
+ self.icon_widget.setLayout(self.icon_layout)
+ self.list_widget.setLayout(self.list_layout)
+
+ self.update()
+
+ # uninstalled
+ elif not self.core.is_installed(widgets[0].game.app_name) and isinstance(widgets[0], BaseInstalledWidget):
+ self.list_layout.removeWidget(widgets[1])
+ self.icon_layout.removeWidget(widgets[0])
+
+ self.widgets.pop(app_name)
+
+ game = self.core.get_game(app_name, True)
+ self.add_uninstalled_widget(game)
+
+ self.icon_layout.addWidget(self.widgets[app_name][0])
+ self.list_layout.addWidget(self.widgets[app_name][1])
+ else:
+ installed_names = [i.app_name for i in self.core.get_installed_list()]
+ # get Uninstalled games
+ uninstalled_games = []
+ games = self.core.get_game_list(True)
+ for game in sorted(games, key=lambda x: x.app_title):
+ if not game.app_name in installed_names:
+ uninstalled_games.append(game.app_name)
+
+ new_installed_games = list(set(installed_names) - set([i.app_name for i in self.installed]))
+ new_uninstalled_games = list(set(uninstalled_games) - set([i.app_name for i in self.uninstalled_names]))
+
+ if (not new_uninstalled_games) and (not new_installed_games):
+ return
+
+ if new_installed_games:
+ for name in new_installed_games:
+ self.icon_layout.removeWidget(self.widgets[app_name][0])
+ self.list_layout.removeWidget(self.widgets[app_name][1])
+
+ self.widgets.pop(name)
+
+ igame = self.core.get_installed_game(name)
+ self.add_installed_widget(igame)
+
+ for name in new_uninstalled_games:
+ self.icon_layout.removeWidget(self.widgets[app_name][0])
+ self.list_layout.removeWidget(self.widgets[app_name][1])
+
+ self.widgets.pop(name)
+
+ game = self.core.get_game(name, True)
+ self.add_uninstalled_widget(game)
+
+ for igame in sorted(self.core.get_installed_list(), key=lambda x: x.title):
+ i_widget, list_widget = self.widgets[igame.app_name]
+
+ self.icon_layout.addWidget(i_widget)
+ self.list_layout.addWidget(list_widget)
+
+ # get Uninstalled games
+ games, self.dlcs = self.core.get_game_and_dlc_list()
+ for game in sorted(games, key=lambda x: x.app_title):
+ if not game.app_name in installed_names:
+ self.uninstalled_names.append(game)
+ for name in uninstalled_games:
+ i_widget, list_widget = self.widgets[name]
+ self.icon_layout.addWidget(i_widget)
+ self.list_layout.addWidget(list_widget)
- if not new_uninstalled_games and not new_installed_games:
- return
- self.removeWidget(self.icon_scrollarea)
- self.removeWidget(self.list_scrollarea)
- self.init_ui(icon_view)
- self.update()
diff --git a/rare/components/tabs/games/game_widgets/base_installed_widget.py b/rare/components/tabs/games/game_widgets/base_installed_widget.py
index acd5a438..db8f9650 100644
--- a/rare/components/tabs/games/game_widgets/base_installed_widget.py
+++ b/rare/components/tabs/games/game_widgets/base_installed_widget.py
@@ -147,4 +147,4 @@ class BaseInstalledWidget(QGroupBox):
print("Cancel Uninstall")
return
legendary_utils.uninstall(self.game.app_name, self.core, infos)
- self.update_list.emit()
+ self.update_list.emit(self.game.app_name)
diff --git a/rare/components/tabs/games/game_widgets/installed_icon_widget.py b/rare/components/tabs/games/game_widgets/installed_icon_widget.py
index 26051a0c..e78e753b 100644
--- a/rare/components/tabs/games/game_widgets/installed_icon_widget.py
+++ b/rare/components/tabs/games/game_widgets/installed_icon_widget.py
@@ -14,7 +14,7 @@ logger = getLogger("GameWidgetInstalled")
class GameWidgetInstalled(BaseInstalledWidget):
- update_list = pyqtSignal()
+ update_list = pyqtSignal(str)
show_info = pyqtSignal(str)
update_game = pyqtSignal()
diff --git a/rare/components/tabs/games/import_widget.py b/rare/components/tabs/games/import_widget.py
index e2e8f7c8..ddb8978c 100644
--- a/rare/components/tabs/games/import_widget.py
+++ b/rare/components/tabs/games/import_widget.py
@@ -16,7 +16,7 @@ logger = getLogger("Import")
class ImportWidget(QWidget):
- update_list = pyqtSignal()
+ update_list = pyqtSignal(str)
def __init__(self, core: LegendaryCore, parent):
super(ImportWidget, self).__init__(parent=parent)
@@ -119,7 +119,7 @@ class ImportWidget(QWidget):
self.core.get_installed_game(app_name).title))
self.app_name_input.setText("")
- self.update_list.emit()
+ self.update_list.emit(app_name)
else:
logger.warning("Failed to import" + app_name)
self.info_label.setText(self.tr("Failed to import {}").format(app_name))
diff --git a/rare/languages/de.ts b/rare/languages/de.ts
index f80c05fe..3d692ad8 100644
--- a/rare/languages/de.ts
+++ b/rare/languages/de.ts
@@ -1362,82 +1362,83 @@ Installationsgröße: {} GB
RareSettings
-
+
Rare Einstellungen
-
+
Speichern
-
+
Ordner für Bilder
+
Sprache
-
+
Starte die App neu um die Änderungen zu aktivieren
-
+
Start des Spiels bestätigen
-
+
Beim verlassen auf das System Tray Icon minimieren
-
+
In das System Tray Icon minimieren
-
+
Speicherstände automatisch mit der Cloud synchronisieren
-
+
Automatisch Synchronisieren
-
+
Größe Speichern
-
+
Die Fenstergröße nach dem Beenden speichern
-
+
Spiele automatisch updaten
-
+
Automatische Updates
-
+
Benachrichtigung nach Abschluss des Downloads anzeigen
-
+
Benachrichtigung anzeigen
@@ -1526,6 +1527,11 @@ Installationsgröße: {} GB
Optionen
+
+
+
+
+
SyncSaves
@@ -1643,22 +1649,22 @@ Installationsgröße: {} GB
Pfad ändern
-
+
Hochladen...
-
+
Hochladen abgeschlossen
-
+
Runterladen...
-
+
Download abgeschlossen
diff --git a/rare/utils/legendary_utils.py b/rare/utils/legendary_utils.py
index 958a0a2e..dac21139 100644
--- a/rare/utils/legendary_utils.py
+++ b/rare/utils/legendary_utils.py
@@ -61,6 +61,12 @@ def uninstall(app_name: str, core: LegendaryCore, options=None):
if os.path.exists(os.path.expanduser(f"~/.local/share/applications/{igame.title}.desktop")):
os.remove(os.path.expanduser(f"~/.local/share/applications/{igame.title}.desktop"))
+ elif os.name == "nt":
+ if os.path.exists(os.path.expanduser(f"~/Desktop/{igame.title.split(':')[0]}.lnk")):
+ os.remove(os.path.expanduser(f"~/Desktop/{igame.title.split(':')[0]}.lnk"))
+ elif os.path.exists(os.path.expandvars(f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk")):
+ os.remove(os.path.expandvars(f"%appdata%/Microsoft/Windows/Start Menu/{igame.title.split(':')[0]}.lnk"))
+
try:
# Remove DLC first so directory is empty when game uninstall runs
dlcs = core.get_dlc_for_game(app_name)
diff --git a/rare/utils/utils.py b/rare/utils/utils.py
index 5c2a5fbc..c09c4f1a 100644
--- a/rare/utils/utils.py
+++ b/rare/utils/utils.py
@@ -14,12 +14,13 @@ if os.name == "nt":
from win32com.client import Dispatch
from rare import lang_path, style_path
+# Mac not supported
+
from custom_legendary.core import LegendaryCore
logger = getLogger("Utils")
s = QSettings("Rare", "Rare")
IMAGE_DIR = s.value("img_dir", os.path.expanduser("~/.cache/rare/images"), type=str)
-logger.info("IMAGE DIRECTORY: " + IMAGE_DIR)
def download_images(signal: pyqtSignal, core: LegendaryCore):
@@ -246,7 +247,8 @@ def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop"):
"Terminal=false\n"
"StartupWMClass=rare-game\n"
)
- os.chmod(os.path.expanduser(f"~/Desktop/{igame.title}.desktop"), 0o755)
+ desktop_file.close()
+ os.chmod(os.path.expanduser(f"{path}{igame.title}.desktop"), 0o755)
# Windows
elif os.name == "nt":
@@ -261,7 +263,12 @@ def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop"):
target = os.path.abspath(sys.argv[0])
# Name of link file
- linkName = igame.title + '.lnk'
+
+ linkName = igame.title
+ for c in r'<>?":|\/*':
+ linkName.replace(c, "")
+
+ linkName = linkName.strip() + '.lnk'
# Path to location of link file
pathLink = os.path.join(target_folder, linkName)
@@ -279,4 +286,5 @@ def create_desktop_link(app_name, core: LegendaryCore, type_of_link="desktop"):
img.save(icon + ".ico")
logger.info("Create Icon")
shortcut.IconLocation = os.path.join(icon + ".ico")
+
shortcut.save()
diff --git a/requirements.txt b/requirements.txt
index 1dfa98c3..67376a60 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,4 +4,5 @@ PyQt5
QtAwesome
notify-py
psutil
-pypresence
\ No newline at end of file
+pypresence
+pywin32; platform_system == "Windows"
diff --git a/setup.py b/setup.py
index 87eb2725..456839c3 100644
--- a/setup.py
+++ b/setup.py
@@ -1,3 +1,5 @@
+import os
+
import setuptools
from rare import __version__ as version
@@ -5,6 +7,21 @@ from rare import __version__ as version
with open("README.md", "r") as fh:
long_description = fh.read()
+requirements = [
+ "requests<3.0",
+ "pillow",
+ "setuptools",
+ "wheel",
+ "PyQt5",
+ "QtAwesome",
+ "notify-py",
+ "psutil",
+ "pypresence"
+ ]
+
+if os.name == "nt":
+ requirements.append("pywin32")
+
setuptools.setup(
name="Rare",
version=version,
@@ -25,15 +42,5 @@ setuptools.setup(
],
python_requires=">=3.8",
entry_points=dict(console_scripts=["rare=rare.__main__:main"]),
- install_requires=[
- "requests<3.0",
- "pillow",
- "setuptools",
- "wheel",
- "PyQt5",
- "QtAwesome",
- "notify-py",
- "psutil",
- "pypresence"
- ],
+ install_requires=requirements,
)