1721677e33
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.
241 lines
7.6 KiB
Python
241 lines
7.6 KiB
Python
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")
|
|
|
|
# lk: delete old Rare directories
|
|
for old_dir in [
|
|
Path(QStandardPaths.writableLocation(QStandardPaths.CacheLocation), "rare").joinpath("tmp"),
|
|
Path(QStandardPaths.writableLocation(QStandardPaths.DataLocation), "rare").joinpath("images"),
|
|
Path(QStandardPaths.writableLocation(QStandardPaths.CacheLocation), "rare"),
|
|
Path(QStandardPaths.writableLocation(QStandardPaths.DataLocation), "rare"),
|
|
]:
|
|
if old_dir.exists():
|
|
# lk: case-sensitive matching on Winblows
|
|
if old_dir.stem in old_dir.parent.iterdir():
|
|
shutil.rmtree(old_dir, ignore_errors=True)
|
|
|
|
|
|
# lk: TempLocation doesn't depend on OrganizationName or ApplicationName
|
|
# lk: so it is fine to use it before initializing the QApplication
|
|
def lock_file() -> Path:
|
|
return Path(QStandardPaths.writableLocation(QStandardPaths.TempLocation), "Rare.lock")
|
|
|
|
|
|
def data_dir() -> Path:
|
|
return Path(QStandardPaths.writableLocation(QStandardPaths.DataLocation))
|
|
|
|
|
|
def cache_dir() -> Path:
|
|
return Path(QStandardPaths.writableLocation(QStandardPaths.CacheLocation))
|
|
|
|
|
|
def image_dir() -> Path:
|
|
return data_dir().joinpath("images")
|
|
|
|
|
|
def log_dir() -> Path:
|
|
return cache_dir().joinpath("logs")
|
|
|
|
|
|
def tmp_dir() -> Path:
|
|
return cache_dir().joinpath("tmp")
|
|
|
|
|
|
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
|