From 1721677e330e48cdb8119b47728743eb668b196d Mon Sep 17 00:00:00 2001
From: loathingKernel <142770+loathingKernel@users.noreply.github.com>
Date: Tue, 7 Feb 2023 13:41:59 +0200
Subject: [PATCH] GameWidget: Implement reactive and interactive labels
The `status_label` displays what is currently going on with the game.
It reflects the current operation running on it or if it requires special
attention (update, needs verification etc)
The `tooltip_label` displays hover information such as what happens
if a part of the widget is clicked or in the case of the launch button if
the game can run (without version check, offline etc)
The context menu on the widgets will be updated and populated according
to the installation state of the game. Since the context menu was revised
the shortcut creation code was revised too to make it more compact.
the `create_desktop_link` and `get_rare_executable` functions are moved
from `rare.utils.misc` to `rare.utils.paths` to avoid cyclical imports and
better grouping. Two functions are added, `desktop_link_path` to uniformly
calculate the path of the shortcut and `desktop_links_supported` which
checks if Rare supports creating shortcuts on the current platform.
`desktop_links_supported` should be used as safeguard before `desktop_link_path`.
Desktop links are currently untested on Windows but if `shortcut.Description`
works as expected, it should be good to go.
---
rare/__main__.py | 6 +-
rare/components/tabs/downloads/__init__.py | 23 +-
rare/components/tabs/downloads/thread.py | 8 +-
.../tabs/games/game_widgets/game_widget.py | 331 +++++++++---------
.../games/game_widgets/icon_game_widget.py | 21 +-
.../tabs/games/game_widgets/icon_widget.py | 26 +-
.../tabs/games/game_widgets/library_widget.py | 2 +
.../games/game_widgets/list_game_widget.py | 61 +---
.../tabs/games/game_widgets/list_widget.py | 4 +
rare/components/tabs/settings/rare.py | 43 +--
rare/models/game.py | 3 +-
rare/utils/misc.py | 162 +--------
rare/utils/paths.py | 191 +++++++++-
13 files changed, 447 insertions(+), 434 deletions(-)
diff --git a/rare/__main__.py b/rare/__main__.py
index b4fda214..c052c7a9 100644
--- a/rare/__main__.py
+++ b/rare/__main__.py
@@ -65,13 +65,13 @@ def main():
args = parser.parse_args()
if args.desktop_shortcut or args.startmenu_shortcut:
- from rare.utils.misc import create_desktop_link
+ from rare.utils.paths import create_desktop_link
if args.desktop_shortcut:
- create_desktop_link(type_of_link="desktop", for_rare=True)
+ create_desktop_link(app_name="rare_shortcut", link_type="desktop")
if args.startmenu_shortcut:
- create_desktop_link(type_of_link="start_menu", for_rare=True)
+ create_desktop_link(app_name="rare_shortcut", link_type="start_menu")
print("Link created")
return
diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py
index fb2878db..ce98aab3 100644
--- a/rare/components/tabs/downloads/__init__.py
+++ b/rare/components/tabs/downloads/__init__.py
@@ -1,4 +1,5 @@
import datetime
+import platform
from ctypes import c_uint64
from logging import getLogger
from typing import Union, Optional
@@ -18,7 +19,8 @@ from rare.models.install import InstallOptionsModel, InstallQueueItemModel, Unin
from rare.shared import RareCore
from rare.shared.workers.install_info import InstallInfoWorker
from rare.shared.workers.uninstall import UninstallWorker
-from rare.utils.misc import get_size, create_desktop_link
+from rare.utils.misc import get_size
+from rare.utils.paths import create_desktop_link, desktop_links_supported
from .download import DownloadWidget
from .groups import UpdateGroup, QueueGroup
from .thread import DlThread, DlResultModel, DlResultCode
@@ -220,15 +222,18 @@ class DownloadsTab(QWidget):
@pyqtSlot(DlResultModel)
def __on_download_result(self, result: DlResultModel):
if result.code == DlResultCode.FINISHED:
- if result.shortcuts:
- if not create_desktop_link(result.options.app_name, self.core, "desktop"):
+ logger.info(f"Download finished: {result.options.app_name}")
+ if result.shortcut and desktop_links_supported():
+ if not create_desktop_link(
+ app_name=result.options.app_name,
+ app_title=result.shortcut_title,
+ link_name=result.shortcut_name,
+ link_type="desktop",
+ ):
# maybe add it to download summary, to show in finished downloads
- pass
+ logger.error(f"Failed to create desktop link on {platform.system()}")
else:
- logger.info("Desktop shortcut written")
- logger.info(
- f"Download finished: {result.options.app_name}"
- )
+ logger.info(f"Created desktop link {result.shortcut_name} for {result.options.app_name}")
if result.options.overlay:
self.signals.application.overlay_installed.emit()
@@ -239,8 +244,8 @@ class DownloadsTab(QWidget):
self.updates_group.set_widget_enabled(result.options.app_name, True)
elif result.code == DlResultCode.ERROR:
- QMessageBox.warning(self, self.tr("Error"), self.tr("Download error: {}").format(result.message))
logger.error(f"Download error: {result.options.app_name} ({result.message})")
+ QMessageBox.warning(self, self.tr("Error"), self.tr("Download error: {}").format(result.message))
elif result.code == DlResultCode.STOPPED:
logger.info(f"Download stopped: {result.options.app_name}")
diff --git a/rare/components/tabs/downloads/thread.py b/rare/components/tabs/downloads/thread.py
index 43fecfb0..a13b6dd9 100644
--- a/rare/components/tabs/downloads/thread.py
+++ b/rare/components/tabs/downloads/thread.py
@@ -34,7 +34,9 @@ class DlResultModel:
dlcs: Optional[List[Dict]] = None
sync_saves: bool = False
tip_url: str = ""
- shortcuts: bool = False
+ shortcut: bool = False
+ shortcut_name: str = ""
+ shortcut_title: str = ""
class DlThread(QThread):
@@ -149,7 +151,9 @@ class DlThread(QThread):
)
if not self.item.options.update and self.item.options.create_shortcut:
- result.shortcuts = True
+ result.shortcut = True
+ result.shortcut_name = self.rgame.folder_name
+ result.shortcut_title = self.rgame.app_title
self.__result_emit(result)
diff --git a/rare/components/tabs/games/game_widgets/game_widget.py b/rare/components/tabs/games/game_widgets/game_widget.py
index 51afed28..1fa69b83 100644
--- a/rare/components/tabs/games/game_widgets/game_widget.py
+++ b/rare/components/tabs/games/game_widgets/game_widget.py
@@ -1,9 +1,8 @@
-import os
import platform
-from abc import abstractmethod
+from abc import ABCMeta
from logging import getLogger
-from PyQt5.QtCore import pyqtSignal, QStandardPaths, Qt, pyqtSlot
+from PyQt5.QtCore import pyqtSignal, Qt, pyqtSlot, QObject, QEvent
from PyQt5.QtGui import QMouseEvent
from PyQt5.QtWidgets import QMessageBox, QAction, QLabel, QPushButton
@@ -14,12 +13,19 @@ from rare.shared import (
ArgumentsSingleton,
ImageManagerSingleton,
)
-from rare.utils.misc import create_desktop_link
+from rare.utils.paths import desktop_links_supported, desktop_link_path, create_desktop_link
from .library_widget import LibraryWidget
logger = getLogger("GameWidget")
+class GameWidgetUi(metaclass=ABCMeta):
+ status_label: QLabel
+ install_btn: QPushButton
+ launch_btn: QPushButton
+ tooltip_label: QLabel
+
+
class GameWidget(LibraryWidget):
show_info = pyqtSignal(RareGame)
@@ -33,86 +39,41 @@ class GameWidget(LibraryWidget):
self.rgame: RareGame = rgame
self.setContextMenuPolicy(Qt.ActionsContextMenu)
- if self.rgame.is_installed or self.rgame.is_origin:
- self.launch_action = QAction(self.tr("Launch"), self)
- self.launch_action.triggered.connect(self._launch)
- self.addAction(self.launch_action)
- else:
- self.install_action = QAction(self.tr("Install"), self)
- self.install_action.triggered.connect(self._install)
- self.addAction(self.install_action)
- # if self.rgame.game.supports_cloud_saves:
- # sync = QAction(self.tr("Sync with cloud"), self)
- # sync.triggered.connect(self.sync_game)
- # self.addAction(sync)
+ self.launch_action = QAction(self.tr("Launch"), self)
+ self.launch_action.triggered.connect(self._launch)
- desktop = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)
- if os.path.exists(
- os.path.join(desktop, f"{self.rgame.app_title}.desktop")
- ) or os.path.exists(os.path.join(desktop, f"{self.rgame.app_title}.lnk")):
- self.create_desktop = QAction(self.tr("Remove Desktop link"))
- else:
- self.create_desktop = QAction(self.tr("Create Desktop link"))
- if self.rgame.is_installed:
- self.create_desktop.triggered.connect(
- lambda: self.create_desktop_link("desktop")
- )
- self.addAction(self.create_desktop)
+ self.install_action = QAction(self.tr("Install"), self)
+ self.install_action.triggered.connect(self._install)
- applications = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation)
- if platform.system() == "Linux":
- start_menu_file = os.path.join(applications, f"{self.rgame.app_title}.desktop")
- elif platform.system() == "Windows":
- start_menu_file = os.path.join(applications, "..", f"{self.rgame.app_title}.lnk")
- else:
- start_menu_file = ""
- if platform.system() in ["Windows", "Linux"]:
- if os.path.exists(start_menu_file):
- self.create_start_menu = QAction(self.tr("Remove start menu link"))
- else:
- self.create_start_menu = QAction(self.tr("Create start menu link"))
- if self.rgame.is_installed:
- self.create_start_menu.triggered.connect(
- lambda: self.create_desktop_link("start_menu")
- )
- self.addAction(self.create_start_menu)
+ # self.sync_action = QAction(self.tr("Sync with cloud"), self)
+ # self.sync_action.triggered.connect(self.sync_saves)
- reload_image = QAction(self.tr("Reload Image"), self)
- reload_image.triggered.connect(self._on_reload_image)
- self.addAction(reload_image)
+ self.desktop_link_action = QAction(self)
+ self.desktop_link_action.triggered.connect(
+ lambda: self._create_link(self.rgame.folder_name, "desktop")
+ )
- if self.rgame.is_installed and not self.rgame.is_origin:
- self.uninstall_action = QAction(self.tr("Uninstall"), self)
- self.uninstall_action.triggered.connect(self._uninstall)
- self.addAction(self.uninstall_action)
+ self.menu_link_action = QAction(self)
+ self.menu_link_action.triggered.connect(
+ lambda: self._create_link(self.rgame.folder_name, "start_menu")
+ )
- self.texts = {
- "hover": {
- "update_available": self.tr("Start without version check"),
- "launch": self.tr("Launch Game"),
- "launch_origin": self.tr("Launch/Link"),
- "running": self.tr("Game running"),
- "launch_offline": self.tr("Launch offline"),
- "no_launch": self.tr("Can't launch game")
- },
- "status": {
- "needs_verification": self.tr("Please verify game before playing"),
- "running": self.tr("Game running"),
- "syncing": self.tr("Syncing cloud saves"),
- "update_available": self.tr("Update available"),
- "no_meta": self.tr("Game is only offline available"),
- "no_launch": self.tr("Can't launch game"),
- },
- }
+ self.reload_action = QAction(self.tr("Reload Image"), self)
+ self.reload_action.triggered.connect(self._on_reload_image)
+
+ self.uninstall_action = QAction(self.tr("Uninstall"), self)
+ self.uninstall_action.triggered.connect(self._uninstall)
+
+ self.update_actions()
# signals
- self.rgame.signals.widget.update.connect(
- lambda: self.setPixmap(self.rgame.pixmap)
- )
- self.rgame.signals.widget.update.connect(
- self.update_widget
- )
+ self.rgame.signals.widget.update.connect(lambda: self.setPixmap(self.rgame.pixmap))
+ self.rgame.signals.widget.update.connect(self.update_buttons)
+ self.rgame.signals.widget.update.connect(self.update_state)
+ self.rgame.signals.game.installed.connect(self.update_actions)
+ self.rgame.signals.game.uninstalled.connect(self.update_actions)
+
self.rgame.signals.progress.start.connect(
lambda: self.showProgress(
self.image_manager.get_pixmap(self.rgame.app_name, True),
@@ -125,67 +86,113 @@ class GameWidget(LibraryWidget):
self.rgame.signals.progress.finish.connect(
lambda e: self.hideProgress(e)
)
- self.rgame.signals.progress.finish.connect(self.set_status)
- @abstractmethod
- def set_status(self, label: QLabel):
- if self.rgame.is_installed:
+ self.state_strings = {
+ RareGame.State.IDLE: "",
+ RareGame.State.RUNNING: self.tr("Running..."),
+ RareGame.State.DOWNLOADING: self.tr("Downloading..."),
+ RareGame.State.VERIFYING: self.tr("Verifying..."),
+ RareGame.State.MOVING: self.tr("Moving..."),
+ RareGame.State.UNINSTALLING: self.tr("Uninstalling..."),
+ "has_update": self.tr("Update available"),
+ "needs_verification": self.tr("Needs verification"),
+ "not_can_launch": self.tr("Can't launch"),
+ }
+
+ self.hover_strings = {
+ "info": self.tr("Show infromation"),
+ "install": self.tr("Install game"),
+ "can_launch": self.tr("Launch game"),
+ "is_foreign": self.tr("Launch offline"),
+ "has_update": self.tr("Launch without version check"),
+ "is_origin": self.tr("Launch/Link"),
+ "not_can_launch": self.tr("Can't launch"),
+ }
+
+ # lk: abstract class for typing, the `self.ui` attribute should be used
+ # lk: by the Ui class in the children. It must contain at least the same
+ # lk: attributes as `GameWidgetUi` class
+ self.ui = GameWidgetUi()
+
+ @pyqtSlot()
+ def update_state(self):
+ if self.rgame.is_idle:
if self.rgame.has_update:
- label.setText(self.texts["status"]["update_available"])
- return
- if self.rgame.needs_verification:
- label.setText(self.texts["status"]["needs_verification"])
- return
- label.setText("")
- label.setVisible(False)
-
- @abstractmethod
- def update_widget(self, install_btn: QPushButton, launch_btn: QPushButton):
- install_btn.setVisible(not self.rgame.is_installed)
- install_btn.setEnabled(not self.rgame.is_installed)
- launch_btn.setVisible(self.rgame.is_installed)
- launch_btn.setEnabled(self.rgame.can_launch)
-
- @property
- def enterEventText(self) -> str:
- if self.rgame.is_installed:
- if self.rgame.state == RareGame.State.RUNNING:
- return self.texts["status"]["running"]
- elif (not self.rgame.is_origin) and self.rgame.needs_verification:
- return self.texts["status"]["needs_verification"]
- elif self.rgame.is_foreign:
- return self.texts["hover"]["launch_offline"]
- elif self.rgame.has_update:
- return self.texts["hover"]["update_available"]
+ self.ui.status_label.setText(self.state_strings["has_update"])
+ elif self.rgame.needs_verification:
+ self.ui.status_label.setText(self.state_strings["needs_verification"])
+ elif not self.rgame.can_launch and self.rgame.is_installed:
+ self.ui.status_label.setText(self.state_strings["not_can_launch"])
else:
- return self.tr("Game Info")
- # return self.texts["hover"]["launch" if self.igame else "launch_origin"]
+ self.ui.status_label.setText(self.state_strings[self.rgame.state])
else:
- if not self.rgame.state == RareGame.State.DOWNLOADING:
- return self.tr("Game Info")
- else:
- return self.tr("Installation running")
+ self.ui.status_label.setText(self.state_strings[self.rgame.state])
+ self.ui.status_label.setVisible(bool(self.ui.status_label.text()))
- @property
- def leaveEventText(self) -> str:
- if self.rgame.is_installed:
- if self.rgame.state == RareGame.State.RUNNING:
- return self.texts["status"]["running"]
- # elif self.syncing_cloud_saves:
- # return self.texts["status"]["syncing"]
- elif self.rgame.is_foreign:
- return self.texts["status"]["no_meta"]
- elif self.rgame.has_update:
- return self.texts["status"]["update_available"]
- elif (not self.rgame.is_origin) and self.rgame.needs_verification:
- return self.texts["status"]["needs_verification"]
- else:
- return ""
+ @pyqtSlot()
+ def update_buttons(self):
+ self.ui.install_btn.setVisible(not self.rgame.is_installed)
+ self.ui.install_btn.setEnabled(not self.rgame.is_installed)
+ self.ui.launch_btn.setVisible(self.rgame.is_installed)
+ self.ui.launch_btn.setEnabled(self.rgame.can_launch)
+
+ @pyqtSlot()
+ def update_actions(self):
+ for action in self.actions():
+ self.removeAction(action)
+
+ if self.rgame.is_installed or self.rgame.is_origin:
+ self.addAction(self.launch_action)
else:
- if self.rgame.state == RareGame.State.DOWNLOADING:
- return "Installation..."
+ self.addAction(self.install_action)
+
+ # if self.rgame.game.supports_cloud_saves:
+ # self.addAction(self.sync_action)
+
+ if desktop_links_supported() and self.rgame.is_installed:
+ if desktop_link_path(self.rgame.folder_name, "desktop").exists():
+ self.desktop_link_action.setText(self.tr("Remove Desktop link"))
else:
- return ""
+ self.desktop_link_action.setText(self.tr("Create Desktop link"))
+ self.addAction(self.desktop_link_action)
+ if desktop_link_path(self.rgame.folder_name, "start_menu").exists():
+ self.menu_link_action.setText(self.tr("Remove Menu link"))
+ else:
+ self.menu_link_action.setText(self.tr("Create Menu link"))
+ self.addAction(self.menu_link_action)
+
+ self.addAction(self.reload_action)
+ if self.rgame.is_installed and not self.rgame.is_origin:
+ self.addAction(self.uninstall_action)
+
+ def eventFilter(self, a0: QObject, a1: QEvent) -> bool:
+ if a0 is self.ui.launch_btn:
+ if a1.type() == QEvent.Enter:
+ if not self.rgame.can_launch:
+ self.ui.tooltip_label.setText(self.hover_strings["not_can_launch"])
+ elif self.rgame.is_origin:
+ self.ui.tooltip_label.setText(self.hover_strings["is_origin"])
+ elif self.rgame.has_update:
+ self.ui.tooltip_label.setText(self.hover_strings["has_update"])
+ elif self.rgame.is_foreign and self.rgame.can_run_offline:
+ self.ui.tooltip_label.setText(self.hover_strings["is_foreign"])
+ elif self.rgame.can_launch:
+ self.ui.tooltip_label.setText(self.hover_strings["can_launch"])
+ return True
+ if a1.type() == QEvent.Leave:
+ self.ui.tooltip_label.setText(self.hover_strings["info"])
+ # return True
+ if a0 is self.ui.install_btn:
+ if a1.type() == QEvent.Enter:
+ self.ui.tooltip_label.setText(self.hover_strings["install"])
+ return True
+ if a1.type() == QEvent.Leave:
+ self.ui.tooltip_label.setText(self.hover_strings["info"])
+ # return True
+ if a0 is self:
+ if a1.type() == QEvent.Enter:
+ self.ui.tooltip_label.setText(self.hover_strings["info"])
+ return super().eventFilter(a0, a1)
def mousePressEvent(self, e: QMouseEvent) -> None:
# left button
@@ -220,46 +227,42 @@ class GameWidget(LibraryWidget):
def _uninstall(self):
self.show_info.emit(self.rgame)
- def create_desktop_link(self, type_of_link):
- if type_of_link == "desktop":
- shortcut_path = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)
- elif type_of_link == "start_menu":
- shortcut_path = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation)
- else:
- return
-
- if platform.system() == "Windows":
- shortcut_path = os.path.join(shortcut_path, f"{self.rgame.app_title}.lnk")
- elif platform.system() == "Linux":
- shortcut_path = os.path.join(shortcut_path, f"{self.rgame.app_title}.desktop")
- else:
+ def _create_link(self, name, link_type):
+ if not desktop_links_supported():
QMessageBox.warning(
self,
- "Warning",
- f"Create a Desktop link is currently not supported on {platform.system()}",
+ self.tr("Warning"),
+ self.tr("Creating shortcuts is currently unsupported on {}").format(platform.system()),
)
return
- if not os.path.exists(shortcut_path):
- try:
- if not create_desktop_link(self.rgame.app_name, self.core, type_of_link):
- return
- except PermissionError:
- QMessageBox.warning(
- self, "Error", "Permission error. Cannot create Desktop Link"
- )
- if type_of_link == "desktop":
- self.create_desktop.setText(self.tr("Remove Desktop link"))
- elif type_of_link == "start_menu":
- self.create_start_menu.setText(self.tr("Remove Start menu link"))
- else:
- if os.path.exists(shortcut_path):
- os.remove(shortcut_path)
+ shortcut_path = desktop_link_path(name, link_type)
- if type_of_link == "desktop":
- self.create_desktop.setText(self.tr("Create Desktop link"))
- elif type_of_link == "start_menu":
- self.create_start_menu.setText(self.tr("Create Start menu link"))
+ if not shortcut_path.exists():
+ try:
+ if not create_desktop_link(
+ app_name=self.rgame.app_name,
+ app_title=self.rgame.app_title,
+ link_name=self.rgame.folder_name,
+ link_type=link_type,
+ ):
+ raise PermissionError
+ except PermissionError:
+ QMessageBox.warning(self, "Error", "Could not create shortcut.")
+ return
+
+ if link_type == "desktop":
+ self.desktop_link_action.setText(self.tr("Remove Desktop link"))
+ elif link_type == "start_menu":
+ self.menu_link_action.setText(self.tr("Remove Start Menu link"))
+ else:
+ if shortcut_path.exists():
+ shortcut_path.unlink(missing_ok=True)
+
+ if link_type == "desktop":
+ self.desktop_link_action.setText(self.tr("Create Desktop link"))
+ elif link_type == "start_menu":
+ self.menu_link_action.setText(self.tr("Create Start Menu link"))
# def sync_finished(self, app_name):
# self.syncing_cloud_saves = False
diff --git a/rare/components/tabs/games/game_widgets/icon_game_widget.py b/rare/components/tabs/games/game_widgets/icon_game_widget.py
index 9f75ea78..13fc7374 100644
--- a/rare/components/tabs/games/game_widgets/icon_game_widget.py
+++ b/rare/components/tabs/games/game_widgets/icon_game_widget.py
@@ -1,7 +1,7 @@
from logging import getLogger
from typing import Optional
-from PyQt5.QtCore import QEvent, pyqtSlot
+from PyQt5.QtCore import QEvent
from rare.models.game import RareGame
from rare.shared.image_manager import ImageSize
@@ -13,13 +13,13 @@ logger = getLogger("IconGameWidget")
class IconGameWidget(GameWidget):
def __init__(self, rgame: RareGame, parent=None):
- super(IconGameWidget, self).__init__(rgame, parent)
+ super().__init__(rgame, parent)
self.setObjectName(f"{rgame.app_name}")
self.setFixedSize(ImageSize.Display)
self.ui = IconWidget()
self.ui.setupUi(self)
- self.ui.title_label.setText(f"
{self.rgame.app_title}
")
+ self.ui.title_label.setText(self.rgame.app_title)
self.ui.launch_btn.clicked.connect(self._launch)
self.ui.launch_btn.setVisible(self.rgame.is_installed)
self.ui.install_btn.clicked.connect(self._install)
@@ -27,27 +27,22 @@ class IconGameWidget(GameWidget):
self.ui.launch_btn.setEnabled(self.rgame.can_launch)
- self.set_status()
+ self.update_state()
- @pyqtSlot()
- def set_status(self):
- super(IconGameWidget, self).set_status(self.ui.status_label)
-
- @pyqtSlot()
- def update_widget(self):
- super(IconGameWidget, self).update_widget(self.ui.install_btn, self.ui.launch_btn)
+ # lk: "connect" the buttons' enter/leave events to this widget
+ self.installEventFilter(self)
+ self.ui.launch_btn.installEventFilter(self)
+ self.ui.install_btn.installEventFilter(self)
def enterEvent(self, a0: Optional[QEvent] = None) -> None:
if a0 is not None:
a0.accept()
- self.ui.tooltip_label.setText(self.enterEventText)
self.ui.enterAnimation(self)
def leaveEvent(self, a0: Optional[QEvent] = None) -> None:
if a0 is not None:
a0.accept()
self.ui.leaveAnimation(self)
- self.ui.tooltip_label.setText(self.leaveEventText)
# def sync_finished(self, app_name):
# if not app_name == self.rgame.app_name:
diff --git a/rare/components/tabs/games/game_widgets/icon_widget.py b/rare/components/tabs/games/game_widgets/icon_widget.py
index e0572e83..8f055a3e 100644
--- a/rare/components/tabs/games/game_widgets/icon_widget.py
+++ b/rare/components/tabs/games/game_widgets/icon_widget.py
@@ -17,15 +17,15 @@ from rare.widgets.elide_label import ElideLabel
class IconWidget(object):
def __init__(self):
self._effect = None
- self._animation = None
+ self._animation: QPropertyAnimation = None
- self.status_label = None
- self.mini_widget = None
- self.mini_effect = None
- self.title_label = None
- self.tooltip_label = None
- self.launch_btn = None
- self.install_btn = None
+ self.status_label: ElideLabel = None
+ self.mini_widget: QWidget = None
+ self.mini_effect: QGraphicsOpacityEffect = None
+ self.title_label: QLabel = None
+ self.tooltip_label: ElideLabel = None
+ self.launch_btn: QPushButton = None
+ self.install_btn: QPushButton = None
def setupUi(self, widget: QWidget):
# information at top
@@ -34,6 +34,7 @@ class IconWidget(object):
self.status_label.setStyleSheet(
f"QLabel#{self.status_label.objectName()}"
"{"
+ "font-weight: bold;"
"color: white;"
"background-color: rgba(0, 0, 0, 65%);"
"border-radius: 5%;"
@@ -68,11 +69,12 @@ class IconWidget(object):
self.title_label.setStyleSheet(
f"QLabel#{self.title_label.objectName()}"
"{"
+ "font-weight: bold;"
"background-color: rgba(0, 0, 0, 0%); color: white;"
"}"
)
self.title_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
- self.title_label.setAlignment(Qt.AlignTop)
+ self.title_label.setAlignment(Qt.AlignVCenter)
self.title_label.setAutoFillBackground(False)
self.title_label.setWordWrap(True)
@@ -125,12 +127,6 @@ class IconWidget(object):
self.install_btn.setFixedSize(QSize(widget.width() // 4, widget.width() // 4))
# Create layouts
- # layout for the whole widget, holds the image
- layout = QVBoxLayout()
- layout.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
- layout.setContentsMargins(0, 0, 0, 0)
- layout.setSizeConstraint(QVBoxLayout.SetFixedSize)
-
# layout for the image, holds the mini widget and a spacer item
image_layout = QVBoxLayout()
image_layout.setContentsMargins(2, 2, 2, 2)
diff --git a/rare/components/tabs/games/game_widgets/library_widget.py b/rare/components/tabs/games/game_widgets/library_widget.py
index d869e797..a2aaebdf 100644
--- a/rare/components/tabs/games/game_widgets/library_widget.py
+++ b/rare/components/tabs/games/game_widgets/library_widget.py
@@ -37,6 +37,7 @@ class ProgressLabel(QLabel):
def setStyleSheetColors(self, bg: QColor, fg: QColor, brd: QColor):
sheet = (
+ f"QLabel#{type(self).__name__} {{"
f"background-color: rgba({bg.red()}, {bg.green()}, {bg.blue()}, 65%);"
f"color: rgb({fg.red()}, {fg.green()}, {fg.blue()});"
f"border-width: 1px;"
@@ -44,6 +45,7 @@ class ProgressLabel(QLabel):
f"border-color: rgb({brd.red()}, {brd.green()}, {brd.blue()});"
f"font-weight: bold;"
f"font-size: 16pt;"
+ f"}}"
)
self.setStyleSheet(sheet)
diff --git a/rare/components/tabs/games/game_widgets/list_game_widget.py b/rare/components/tabs/games/game_widgets/list_game_widget.py
index 50825eb9..a3acf574 100644
--- a/rare/components/tabs/games/game_widgets/list_game_widget.py
+++ b/rare/components/tabs/games/game_widgets/list_game_widget.py
@@ -1,13 +1,15 @@
from logging import getLogger
-from PyQt5.QtCore import Qt, QEvent, QRect, pyqtSlot, pyqtSignal
+from PyQt5.QtCore import Qt, QEvent, QRect
from PyQt5.QtGui import (
QPalette,
QBrush,
QPaintEvent,
QPainter,
QLinearGradient,
- QPixmap, QImage, QResizeEvent,
+ QPixmap,
+ QImage,
+ QResizeEvent,
)
from rare.models.game import RareGame
@@ -20,72 +22,45 @@ logger = getLogger("ListGameWidget")
class ListGameWidget(GameWidget):
-
def __init__(self, rgame: RareGame, parent=None):
- super(ListGameWidget, self).__init__(rgame, parent)
+ super().__init__(rgame, parent)
self.setObjectName(f"{rgame.app_name}")
self.ui = ListWidget()
self.ui.setupUi(self)
- self.ui.title_label.setText(f"{self.rgame.app_title}
")
-
- self.ui.install_btn.setVisible(not self.rgame.is_installed)
+ self.ui.title_label.setText(self.rgame.app_title)
+ self.ui.launch_btn.clicked.connect(self._launch)
+ self.ui.launch_btn.setVisible(self.rgame.is_installed)
self.ui.install_btn.clicked.connect(self._install)
+ self.ui.install_btn.setVisible(not self.rgame.is_installed)
+
+ self.ui.launch_btn.setEnabled(self.rgame.can_launch)
self.ui.launch_btn.setText(
self.tr("Launch") if not self.rgame.is_origin else self.tr("Link/Play")
)
- self.ui.launch_btn.clicked.connect(self._launch)
- self.ui.launch_btn.setVisible(self.rgame.is_installed)
-
self.ui.developer_text.setText(self.rgame.developer)
# self.version_label.setVisible(self.is_installed)
if self.rgame.igame:
self.ui.version_text.setText(self.rgame.version)
-
self.ui.size_text.setText(get_size(self.rgame.install_size) if self.rgame.install_size else "")
- self.ui.launch_btn.setEnabled(self.rgame.can_launch)
+ self.update_state()
- self.set_status()
-
- @pyqtSlot()
- def set_status(self):
- super(ListGameWidget, self).set_status(self.ui.status_label)
-
- @pyqtSlot()
- def update_widget(self):
- super(ListGameWidget, self).update_widget(self.ui.install_btn, self.ui.launch_btn)
-
- def update_text(self, e=None):
- if self.rgame.is_installed:
- if self.rgame.has_update:
- self.ui.status_label.setText(self.texts["status"]["update_available"])
- elif self.rgame.is_foreign:
- self.ui.status_label.setText(self.texts["status"]["no_meta"])
- elif self.rgame.igame and self.rgame.needs_verification:
- self.ui.status_label.setText(self.texts["status"]["needs_verification"])
- # elif self.syncing_cloud_saves:
- # self.ui.status_label.setText(self.texts["status"]["syncing"])
- else:
- self.ui.status_label.setText("")
- self.ui.status_label.setVisible(False)
- else:
- self.ui.status_label.setVisible(False)
+ # lk: "connect" the buttons' enter/leave events to this widget
+ self.installEventFilter(self)
+ self.ui.launch_btn.installEventFilter(self)
+ self.ui.install_btn.installEventFilter(self)
def enterEvent(self, a0: QEvent = None) -> None:
if a0 is not None:
a0.accept()
- status = self.enterEventText
- self.ui.tooltip_label.setText(status)
- self.ui.tooltip_label.setVisible(bool(status))
+ self.ui.tooltip_label.setVisible(True)
def leaveEvent(self, a0: QEvent = None) -> None:
if a0 is not None:
a0.accept()
- status = self.leaveEventText
- self.ui.tooltip_label.setText(status)
- self.ui.tooltip_label.setVisible(bool(status))
+ self.ui.tooltip_label.setVisible(False)
# def game_started(self, app_name):
# if app_name == self.rgame.app_name:
diff --git a/rare/components/tabs/games/game_widgets/list_widget.py b/rare/components/tabs/games/game_widgets/list_widget.py
index e5bdbf5c..ae7a5a85 100644
--- a/rare/components/tabs/games/game_widgets/list_widget.py
+++ b/rare/components/tabs/games/game_widgets/list_widget.py
@@ -29,10 +29,14 @@ class ListWidget(object):
def setupUi(self, widget: QWidget):
self.title_label = QLabel(parent=widget)
self.title_label.setWordWrap(False)
+ self.title_label.setStyleSheet(
+ "font-weight: bold;"
+ )
self.status_label = QLabel(parent=widget)
self.status_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
self.status_label.setStyleSheet(
+ "font-weight: bold;"
"background-color: rgba(0,0,0,75%);"
"border: 1px solid black;"
"border-radius: 5px;"
diff --git a/rare/components/tabs/settings/rare.py b/rare/components/tabs/settings/rare.py
index 44b1f407..84009dcf 100644
--- a/rare/components/tabs/settings/rare.py
+++ b/rare/components/tabs/settings/rare.py
@@ -4,13 +4,12 @@ import subprocess
import sys
from logging import getLogger
-from PyQt5.QtCore import QSettings, QStandardPaths, Qt
+from PyQt5.QtCore import QSettings, Qt
from PyQt5.QtWidgets import QWidget, QMessageBox
-from rare.shared import LegendaryCoreSingleton
from rare.components.tabs.settings.widgets.rpc import RPCSettings
+from rare.shared import LegendaryCoreSingleton
from rare.ui.components.tabs.settings.rare import Ui_RareSettings
-from rare.utils.paths import log_dir
from rare.utils.misc import (
get_translations,
get_color_schemes,
@@ -18,8 +17,8 @@ from rare.utils.misc import (
get_style_sheets,
set_style_sheet,
get_size,
- create_desktop_link,
)
+from rare.utils.paths import create_desktop_link, desktop_link_path, log_dir, desktop_links_supported
logger = getLogger("RareSettings")
@@ -106,36 +105,28 @@ class RareSettings(QWidget, Ui_RareSettings):
)
)
self.notification.stateChanged.connect(
- lambda: self.settings.setValue(
- "notification", self.notification.isChecked()
- )
+ lambda: self.settings.setValue("notification", self.notification.isChecked())
)
self.save_size.stateChanged.connect(self.save_window_size)
self.log_games.stateChanged.connect(
lambda: self.settings.setValue("show_console", self.log_games.isChecked())
)
- desktop = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)
- applications = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation)
- if platform.system() == "Linux":
- self.desktop_file = os.path.join(desktop, "Rare.desktop")
- self.start_menu_link = os.path.join(applications, "Rare.desktop")
- elif platform.system() == "Windows":
- self.desktop_file = os.path.join(desktop, "Rare.lnk")
- self.start_menu_link = os.path.join(applications, "..", "Rare.lnk")
+ if desktop_links_supported():
+ self.desktop_link = desktop_link_path("Rare", "desktop")
+ self.start_menu_link = desktop_link_path("Rare", "start_menu")
else:
- self.desktop_link_btn.setText(self.tr("Not supported"))
+ self.desktop_link_btn.setToolTip(self.tr("Not supported"))
self.desktop_link_btn.setDisabled(True)
- self.startmenu_link_btn.setText(self.tr("Not supported"))
+ self.startmenu_link_btn.setToolTip(self.tr("Not supported"))
self.startmenu_link_btn.setDisabled(True)
-
- self.desktop_file = ""
+ self.desktop_link = ""
self.start_menu_link = ""
- if self.desktop_file and os.path.exists(self.desktop_file):
+ if self.desktop_link and self.desktop_link.exists():
self.desktop_link_btn.setText(self.tr("Remove desktop link"))
- if self.start_menu_link and os.path.exists(self.start_menu_link):
+ if self.start_menu_link and self.start_menu_link.exists():
self.startmenu_link_btn.setText(self.tr("Remove start menu link"))
self.desktop_link_btn.clicked.connect(self.create_desktop_link)
@@ -171,7 +162,8 @@ class RareSettings(QWidget, Ui_RareSettings):
def create_start_menu_link(self):
try:
if not os.path.exists(self.start_menu_link):
- create_desktop_link(type_of_link="start_menu", for_rare=True)
+ if not create_desktop_link(app_name="rare_shortcut", link_type="start_menu"):
+ return
self.startmenu_link_btn.setText(self.tr("Remove start menu link"))
else:
os.remove(self.start_menu_link)
@@ -186,11 +178,12 @@ class RareSettings(QWidget, Ui_RareSettings):
def create_desktop_link(self):
try:
- if not os.path.exists(self.desktop_file):
- create_desktop_link(type_of_link="desktop", for_rare=True)
+ if not os.path.exists(self.desktop_link):
+ if not create_desktop_link(app_name="rare_shortcut", link_type="desktop"):
+ return
self.desktop_link_btn.setText(self.tr("Remove Desktop link"))
else:
- os.remove(self.desktop_file)
+ os.remove(self.desktop_link)
self.desktop_link_btn.setText(self.tr("Create desktop link"))
except PermissionError as e:
logger.error(str(e))
diff --git a/rare/models/game.py b/rare/models/game.py
index 8da0c23f..57ebbedd 100644
--- a/rare/models/game.py
+++ b/rare/models/game.py
@@ -14,8 +14,7 @@ from rare.lgndr.core import LegendaryCore
from rare.models.install import InstallOptionsModel, UninstallOptionsModel
from rare.shared.game_process import GameProcess
from rare.shared.image_manager import ImageManager
-from rare.utils.misc import get_rare_executable
-from rare.utils.paths import data_dir
+from rare.utils.paths import data_dir, get_rare_executable
logger = getLogger("RareGame")
diff --git a/rare/utils/misc.py b/rare/utils/misc.py
index f5594010..bef7591e 100644
--- a/rare/utils/misc.py
+++ b/rare/utils/misc.py
@@ -1,7 +1,5 @@
import os
import platform
-import shlex
-import sys
from logging import getLogger
from typing import List, Union, Type
@@ -12,23 +10,19 @@ from PyQt5.QtCore import (
QObject,
QRunnable,
QSettings,
- QStandardPaths,
QFile,
- QDir, Qt,
+ QDir,
+ Qt,
)
-from PyQt5.QtGui import QPalette, QColor, QImage, QFontMetrics
-from PyQt5.QtWidgets import qApp, QStyleFactory, QWidget, QLabel
+from PyQt5.QtGui import QPalette, QColor, QFontMetrics
+from PyQt5.QtWidgets import qApp, QStyleFactory, QLabel
from PyQt5.sip import wrappertype
from legendary.core import LegendaryCore
from legendary.models.game import Game
from requests.exceptions import HTTPError
from rare.models.apiresults import ApiResults
-from rare.utils.paths import image_dir, resources_path
-
-if platform.system() == "Windows":
- # noinspection PyUnresolvedReferences
- from win32com.client import Dispatch # pylint: disable=E0401
+from rare.utils.paths import resources_path
logger = getLogger("Utils")
settings = QSettings("Rare", "Rare")
@@ -156,152 +150,6 @@ def get_size(b: Union[int, float]) -> str:
b /= 1024
-def get_rare_executable() -> List[str]:
- # lk: detect if nuitka
- if "__compiled__" in globals():
- executable = [sys.executable]
- elif platform.system() == "Linux" or platform.system() == "Darwin":
- if p := os.environ.get("APPIMAGE"):
- executable = [p]
- else:
- if sys.executable == os.path.abspath(sys.argv[0]):
- executable = [sys.executable]
- else:
- executable = [sys.executable, os.path.abspath(sys.argv[0])]
- elif platform.system() == "Windows":
- executable = [sys.executable]
-
- if sys.executable != os.path.abspath(sys.argv[0]):
- executable.append(os.path.abspath(sys.argv[0]))
-
- if executable[0].endswith("python.exe"):
- # be sure to start consoleless then
- executable[0] = executable[0].replace("python.exe", "pythonw.exe")
- if executable[1].endswith("rare"):
- executable[1] = executable[1] + ".exe"
- else:
- executable = [sys.executable]
-
- executable[0] = os.path.abspath(executable[0])
- return executable
-
-
-def create_desktop_link(app_name=None, core: LegendaryCore = None, type_of_link="desktop",
- for_rare: bool = False) -> bool:
- if not for_rare:
- igame = core.get_installed_game(app_name)
-
- icon = os.path.join(os.path.join(image_dir(), igame.app_name, "installed.png"))
- icon = icon.replace(".png", "")
-
- if platform.system() == "Linux":
- if type_of_link == "desktop":
- path = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)
- elif type_of_link == "start_menu":
- path = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation)
- else:
- return False
- if not os.path.exists(path):
- return False
- executable = get_rare_executable()
- executable = shlex.join(executable)
-
- if for_rare:
- with open(os.path.join(path, "Rare.desktop"), "w") as desktop_file:
- desktop_file.write(
- "[Desktop Entry]\n"
- f"Name=Rare\n"
- f"Type=Application\n"
- f"Categories=Game;\n"
- f"Icon={os.path.join(resources_path, 'images', 'Rare.png')}\n"
- f"Exec={executable}\n"
- "Terminal=false\n"
- "StartupWMClass=Rare\n"
- )
- else:
- with open(os.path.join(path, f"{igame.title}.desktop"), "w") as desktop_file:
- desktop_file.write(
- "[Desktop Entry]\n"
- f"Name={igame.title}\n"
- f"Type=Application\n"
- f"Categories=Game;\n"
- f"Icon={icon}.png\n"
- f"Exec={executable} launch {app_name}\n"
- "Terminal=false\n"
- "StartupWMClass=Rare\n"
- )
- os.chmod(os.path.join(path, f"{igame.title}.desktop"), 0o755)
-
- return True
-
- elif platform.system() == "Windows":
- # Target of shortcut
- if type_of_link == "desktop":
- target_folder = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)
- elif type_of_link == "start_menu":
- target_folder = os.path.join(
- QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation),
- ".."
- )
- else:
- logger.warning("No valid type of link")
- return False
- if not os.path.exists(target_folder):
- return False
-
- if for_rare:
- linkName = "Rare.lnk"
- else:
- linkName = igame.title
- # TODO: this conversion is not applied everywhere (see base_installed_widget), should it?
- for c in r'<>?":|\/*':
- linkName = linkName.replace(c, "")
-
- linkName = f"{linkName.strip()}.lnk"
-
- # Path to location of link file
- pathLink = os.path.join(target_folder, linkName)
-
- # Add shortcut
- shell = Dispatch("WScript.Shell")
- shortcut = shell.CreateShortCut(pathLink)
-
- executable = get_rare_executable()
- arguments = []
-
- if len(executable) > 1:
- arguments.extend(executable[1:])
- executable = executable[0]
-
- if not for_rare:
- arguments.extend(["launch", app_name])
-
- shortcut.Targetpath = executable
- # Maybe there is a better solution, but windows does not accept single quotes (Windows is weird)
- shortcut.Arguments = shlex.join(arguments).replace("'", '"')
- if for_rare:
- shortcut.WorkingDirectory = QStandardPaths.writableLocation(QStandardPaths.HomeLocation)
-
- # Icon
- if for_rare:
- icon_location = os.path.join(resources_path, "images", "Rare.ico")
- else:
- if not os.path.exists(f"{icon}.ico"):
- img = QImage()
- img.load(f"{icon}.png")
- img.save(f"{icon}.ico")
- logger.info("Created ico file")
- icon_location = f"{icon}.ico"
- shortcut.IconLocation = os.path.abspath(icon_location)
-
- shortcut.save()
- return True
-
- # mac OS is based on Darwin
- elif platform.system() == "Darwin":
- return False
-
-
class CloudWorker(QRunnable):
class Signals(QObject):
# List[SaveGameFile]
diff --git a/rare/utils/paths.py b/rare/utils/paths.py
index ba342ec7..dab3d912 100644
--- a/rare/utils/paths.py
+++ b/rare/utils/paths.py
@@ -1,8 +1,20 @@
import os
+import platform
+import shlex
import shutil
+import sys
+from logging import getLogger
from pathlib import Path
+from typing import List
from PyQt5.QtCore import QStandardPaths
+from PyQt5.QtGui import QImage
+
+if platform.system() == "Windows":
+ # noinspection PyUnresolvedReferences
+ from win32com.client import Dispatch # pylint: disable=E0401
+
+logger = getLogger("Paths")
resources_path = Path(__file__).absolute().parent.parent.joinpath("resources")
@@ -15,7 +27,7 @@ for old_dir in [
]:
if old_dir.exists():
# lk: case-sensitive matching on Winblows
- if old_dir.stem in os.listdir(old_dir.parent):
+ if old_dir.stem in old_dir.parent.iterdir():
shutil.rmtree(old_dir, ignore_errors=True)
@@ -49,3 +61,180 @@ def create_dirs() -> None:
for path in (data_dir(), cache_dir(), image_dir(), log_dir(), tmp_dir()):
if not path.exists():
path.mkdir(parents=True)
+
+
+def home_dir() -> Path:
+ return Path(QStandardPaths.writableLocation(QStandardPaths.HomeLocation))
+
+
+def desktop_dir() -> Path:
+ return Path(QStandardPaths.writableLocation(QStandardPaths.DesktopLocation))
+
+
+def applications_dir() -> Path:
+ return Path(QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation))
+
+
+__link_suffix = {
+ "Windows": ".lnk",
+ "Linux": ".desktop",
+}
+
+
+def desktop_links_supported() -> bool:
+ return platform.system() in __link_suffix.keys()
+
+
+__link_type = {
+ "desktop": desktop_dir(),
+ # lk: for some undocumented reason, on Windows we used the parent directory
+ # lk: for start menu items. Mirror it here for backwards compatibility
+ "start_menu": applications_dir().parent if platform.system() == "Windows" else applications_dir(),
+}
+
+
+def desktop_link_path(link_name: str, link_type: str) -> Path:
+ """
+ Creates the path of a shortcut link
+
+ :param link_name:
+ Name of the shortcut file
+ :param link_type:
+ "desktop" or "start_menu"
+ :return Path:
+ shortcut path
+ """
+ return __link_type[link_type].joinpath(f"{link_name}{__link_suffix[platform.system()]}")
+
+
+def get_rare_executable() -> List[str]:
+ # lk: detect if nuitka
+ if "__compiled__" in globals():
+ executable = [sys.executable]
+ elif platform.system() == "Linux" or platform.system() == "Darwin":
+ if p := os.environ.get("APPIMAGE"):
+ executable = [p]
+ else:
+ if sys.executable == os.path.abspath(sys.argv[0]):
+ executable = [sys.executable]
+ else:
+ executable = [sys.executable, os.path.abspath(sys.argv[0])]
+ elif platform.system() == "Windows":
+ executable = [sys.executable]
+
+ if sys.executable != os.path.abspath(sys.argv[0]):
+ executable.append(os.path.abspath(sys.argv[0]))
+
+ if executable[0].endswith("python.exe"):
+ # be sure to start consoleless then
+ executable[0] = executable[0].replace("python.exe", "pythonw.exe")
+ if executable[1].endswith("rare"):
+ executable[1] = executable[1] + ".exe"
+ else:
+ executable = [sys.executable]
+
+ executable[0] = os.path.abspath(executable[0])
+ return executable
+
+
+def create_desktop_link(app_name: str, app_title: str = "", link_name: str = "", link_type="desktop") -> bool:
+ """
+ Creates a desktop or start menu shortcut link
+
+ :param app_name:
+ app_name or "rare_shortcut" for Rare itself
+ :param app_title:
+ the title shown in the shortcut
+ (overrides to "Rare" for "rare_shortcut")
+ :param link_name:
+ the sanitized filename of the shortcut (use the folder_name attribute of RareGame)
+ (overrides to "Rare" for "rare_shortcut")
+ :param link_type:
+ "desktop" or "start_menu"
+ :return bool:
+ True if successful else False
+ """
+ # macOS is based on Darwin
+ if not desktop_links_supported():
+ logger.error(f"Shortcut creation is not available on {platform.system()}")
+ return False
+
+ if link_type not in ["desktop", "start_menu"]:
+ logger.error(f"Invalid link type {link_type}")
+ return False
+
+ for_rare = app_name == "rare_shortcut"
+
+ if for_rare:
+ icon_path = resources_path.joinpath("images", "Rare.png")
+ if platform.system() == "Windows":
+ icon_path = resources_path.joinpath("images", "Rare.ico")
+ app_title = "Rare"
+ link_name = "Rare"
+ else:
+ icon_path = image_dir().joinpath(app_name, "installed.png")
+ if platform.system() == "Windows":
+ icon_path = image_dir().joinpath(app_name, "installed.ico")
+ if not icon_path.exists():
+ img = QImage()
+ img.load(str(icon_path.with_suffix(".png")))
+ img.save(str(icon_path))
+ logger.info("Created ico file")
+ if not app_title or not link_name:
+ logger.error("Missing app_title or link_name")
+ return False
+
+ shortcut_path = desktop_link_path(link_name, link_type)
+ if not shortcut_path.parent.exists():
+ logger.error(f"Parent directory {shortcut_path.parent} does not exist")
+ return False
+ else:
+ logger.info(f"Creating shortcut for {app_title} at {shortcut_path}")
+
+ if platform.system() == "Linux":
+ executable = get_rare_executable()
+ executable = shlex.join(executable)
+ if not for_rare:
+ executable = f"{executable} launch {app_name}"
+
+ with shortcut_path.open(mode="w") as desktop_file:
+ desktop_file.write(
+ "[Desktop Entry]\n"
+ f"Name={app_title}\n"
+ "Type=Application\n"
+ "Categories=Game;\n"
+ f"Icon={icon_path}\n"
+ f"Exec={executable}\n"
+ "Terminal=false\n"
+ "StartupWMClass=Rare\n"
+ )
+ # shortcut_path.chmod(0o755)
+ return True
+
+ elif platform.system() == "Windows":
+ # Add shortcut
+ shell = Dispatch("WScript.Shell")
+ shortcut = shell.CreateShortCut(str(shortcut_path))
+
+ executable = get_rare_executable()
+ arguments = []
+
+ if len(executable) > 1:
+ arguments.extend(executable[1:])
+ executable = executable[0]
+
+ if not for_rare:
+ arguments.extend(["launch", app_name])
+ shortcut.Targetpath = executable
+ # Maybe there is a better solution, but windows does not accept single quotes (Windows is weird)
+ shortcut.Arguments = shlex.join(arguments).replace("'", '"')
+
+ shortcut.Description = app_title
+ if for_rare:
+ shortcut.WorkingDirectory = str(home_dir())
+
+ # Icon
+ shortcut.IconLocation = icon_path.absolute()
+
+ shortcut.save()
+ return True