1
0
Fork 0
mirror of synced 2024-05-19 12:02:54 +12:00
Rare/rare/utils/misc.py

431 lines
13 KiB
Python
Raw Normal View History

2021-02-10 23:48:25 +13:00
import os
import platform
import shlex
import subprocess
2021-04-14 02:56:44 +12:00
import sys
2021-02-10 23:48:25 +13:00
from logging import getLogger
2022-08-14 03:53:00 +12:00
from typing import List, Union
2021-02-10 23:48:25 +13:00
import qtawesome
import requests
2021-12-24 22:09:50 +13:00
from PyQt5.QtCore import (
pyqtSignal,
pyqtSlot,
QObject,
QRunnable,
QSettings,
QStandardPaths,
2021-12-24 22:09:50 +13:00
QFile,
QDir,
)
from PyQt5.QtGui import QPalette, QColor, QImage
from PyQt5.QtWidgets import qApp, QStyleFactory
from legendary.models.game import Game
from requests.exceptions import HTTPError
from .models import PathSpec
2021-05-12 03:29:35 +12:00
# Windows
if platform.system() == "Windows":
# noinspection PyUnresolvedReferences
from win32com.client import Dispatch # pylint: disable=E0401
2021-05-12 03:29:35 +12:00
from rare.shared import LegendaryCoreSingleton, ApiResultsSingleton
from rare.utils.paths import image_dir, resources_path
2021-12-24 22:09:50 +13:00
2021-05-21 23:20:58 +12:00
# Mac not supported
2021-05-12 19:19:50 +12:00
from legendary.core import LegendaryCore
2021-02-10 23:48:25 +13:00
logger = getLogger("Utils")
2021-08-29 02:01:36 +12:00
settings = QSettings("Rare", "Rare")
2021-03-19 00:45:59 +13:00
color_role_map = {
0: "WindowText",
1: "Button",
2: "Light",
3: "Midlight",
4: "Dark",
5: "Mid",
6: "Text",
7: "BrightText",
8: "ButtonText",
9: "Base",
10: "Window",
11: "Shadow",
12: "Highlight",
13: "HighlightedText",
14: "Link",
15: "LinkVisited",
16: "AlternateBase",
# 17: "NoRole",
18: "ToolTipBase",
19: "ToolTipText",
20: "PlaceholderText",
# 21: "NColorRoles",
}
color_group_map = {
0: "Active",
1: "Disabled",
2: "Inactive",
}
def load_color_scheme(path: str) -> QPalette:
palette = QPalette()
scheme = QSettings(path, QSettings.IniFormat)
try:
scheme.beginGroup("ColorScheme")
for g in color_group_map:
scheme.beginGroup(color_group_map[g])
group = QPalette.ColorGroup(g)
for r in color_role_map:
role = QPalette.ColorRole(r)
color = scheme.value(color_role_map[r], None)
if color is not None:
palette.setColor(group, role, QColor(color))
else:
palette.setColor(group, role, palette.color(QPalette.Active, role))
scheme.endGroup()
scheme.endGroup()
except:
palette = None
return palette
def set_color_pallete(color_scheme: str):
if not color_scheme:
qApp.setStyle(QStyleFactory.create(qApp.property("rareDefaultQtStyle")))
qApp.setStyleSheet("")
qApp.setPalette(qApp.style().standardPalette())
return
qApp.setStyle(QStyleFactory.create("Fusion"))
2021-12-10 09:59:07 +13:00
custom_palette = load_color_scheme(f":/schemes/{color_scheme}")
if custom_palette is not None:
qApp.setPalette(custom_palette)
icon_color = qApp.palette().color(QPalette.Foreground).name()
qtawesome.set_defaults(color=icon_color)
def get_color_schemes() -> List[str]:
colors = []
2021-12-10 09:59:07 +13:00
for file in QDir(":/schemes"):
2022-02-01 15:16:29 +13:00
colors.append(file)
return colors
def set_style_sheet(style_sheet: str):
if not style_sheet:
qApp.setStyle(QStyleFactory.create(qApp.property("rareDefaultQtStyle")))
qApp.setStyleSheet("")
return
qApp.setStyle(QStyleFactory.create("Fusion"))
file = QFile(f":/stylesheets/{style_sheet}/stylesheet.qss")
2021-12-10 09:59:07 +13:00
file.open(QFile.ReadOnly)
stylesheet = file.readAll().data().decode("utf-8")
qApp.setStyleSheet(stylesheet)
icon_color = qApp.palette().color(QPalette.Text).name()
qtawesome.set_defaults(color="#eeeeee")
def get_style_sheets() -> List[str]:
styles = []
2021-12-10 09:59:07 +13:00
for file in QDir(":/stylesheets/"):
styles.append(file)
return styles
def get_translations():
2021-02-20 00:57:55 +13:00
langs = ["en"]
2021-12-11 08:05:51 +13:00
for i in os.listdir(os.path.join(resources_path, "languages")):
if i.endswith(".qm") and not i.startswith("qt_"):
2021-02-20 00:57:55 +13:00
langs.append(i.split(".")[0])
return langs
def get_latest_version():
2021-04-20 01:44:28 +12:00
try:
2021-12-24 22:09:50 +13:00
resp = requests.get(
"https://api.github.com/repos/Dummerle/Rare/releases/latest"
)
tag = resp.json()["tag_name"]
2021-04-20 01:44:28 +12:00
return tag
except requests.exceptions.ConnectionError:
return "0.0.0"
2021-04-12 07:02:56 +12:00
2022-08-14 03:53:00 +12:00
def get_size(b: Union[int, float]) -> str:
2021-12-24 22:09:50 +13:00
for i in ["", "K", "M", "G", "T", "P", "E"]:
2021-04-12 07:02:56 +12:00
if b < 1024:
return f"{b:.2f}{i}B"
b /= 1024
2021-04-14 02:56:44 +12:00
def get_rare_executable() -> List[str]:
2022-06-28 02:47:55 +12:00
if platform.system() == "Linux" or platform.system() == "Darwin":
# TODO flatpak
if p := os.environ.get("APPIMAGE"):
executable = [p]
else:
2022-06-28 02:47:55 +12:00
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 not sys.executable.endswith("Rare.exe"):
# be sure to start consoleless then
executable[0] = executable[0].replace("python.exe", "pythonw.exe")
2022-06-12 02:59:53 +12:00
executable.extend(["-m", "rare"])
2022-06-28 02:47:55 +12:00
else:
executable = [sys.executable]
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":
2021-04-14 04:01:25 +12:00
if type_of_link == "desktop":
2022-04-01 08:29:31 +13:00
path = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)
2021-04-14 04:01:25 +12:00
elif type_of_link == "start_menu":
2022-04-01 08:29:31 +13:00
path = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation)
2021-04-14 04:01:25 +12:00
else:
return False
if not os.path.exists(path):
return False
executable = get_rare_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:
2022-04-01 08:29:31 +13:00
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-game\n"
)
2022-04-01 08:29:31 +13:00
os.chmod(os.path.join(path, f"{igame.title}.desktop"), 0o755)
return True
elif platform.system() == "Windows":
2021-05-12 03:29:35 +12:00
# Target of shortcut
if type_of_link == "desktop":
target_folder = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)
elif type_of_link == "start_menu":
2022-04-01 08:29:31 +13:00
target_folder = os.path.join(
QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation),
".."
)
else:
2021-05-12 03:29:35 +12:00
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
2022-04-01 08:29:31 +13:00
# TODO: this conversion is not applied everywhere (see base_installed_widget), should it?
for c in r'<>?":|\/*':
linkName = linkName.replace(c, "")
2021-05-17 21:38:30 +12:00
linkName = f"{linkName.strip()}.lnk"
2021-05-12 03:29:35 +12:00
# Path to location of link file
pathLink = os.path.join(target_folder, linkName)
# Add shortcut
2021-12-24 22:09:50 +13:00
shell = Dispatch("WScript.Shell")
2021-05-12 03:29:35 +12:00
shortcut = shell.CreateShortCut(pathLink)
executable = get_rare_executable()
arguments = []
if len(executable) > 1:
arguments.extend(executable[1:])
executable = executable[0]
if not sys.executable.endswith("Rare.exe"):
# be sure to start consoleless then
executable = sys.executable.replace("python.exe", "pythonw.exe")
arguments.append(os.path.abspath(sys.argv[0]))
if not for_rare:
arguments.extend(["launch", app_name])
shortcut.Targetpath = executable
shortcut.Arguments = shlex.join(arguments)
# Maybe there is a better solution, but windows does not accept single quotes (Windows is weird)
shortcut.Arguments = shortcut.Arguments.replace("'", '"')
if for_rare:
shortcut.WorkingDirectory = QStandardPaths.writableLocation(QStandardPaths.HomeLocation)
2021-05-12 03:29:35 +12:00
# 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)
2021-05-12 19:19:50 +12:00
2021-05-12 03:29:35 +12:00
shortcut.save()
return True
# mac OS is based on Darwin
elif platform.system() == "Darwin":
return False
2021-08-17 09:08:15 +12:00
class WineResolverSignals(QObject):
result_ready = pyqtSignal(str)
class WineResolver(QRunnable):
def __init__(self, path: str, app_name: str):
super(WineResolver, self).__init__()
self.signals = WineResolverSignals()
self.setAutoDelete(True)
self.wine_env = os.environ.copy()
core = LegendaryCoreSingleton()
self.wine_env.update(core.get_app_environment(app_name))
2021-12-24 22:09:50 +13:00
self.wine_env["WINEDLLOVERRIDES"] = "winemenubuilder=d;mscoree=d;mshtml=d;"
self.wine_env["DISPLAY"] = ""
self.wine_binary = core.lgd.config.get(
2021-12-24 22:09:50 +13:00
app_name,
"wine_executable",
fallback=core.lgd.config.get("default", "wine_executable", fallback="wine"),
)
self.winepath_binary = os.path.join(
os.path.dirname(self.wine_binary), "winepath"
)
self.path = PathSpec(core, app_name).cook(path)
@pyqtSlot()
def run(self):
2021-12-24 22:09:50 +13:00
if "WINEPREFIX" not in self.wine_env or not os.path.exists(
self.wine_env["WINEPREFIX"]
2021-12-24 22:09:50 +13:00
):
# pylint: disable=E1136
self.signals.result_ready[str].emit(str())
return
2021-12-24 22:09:50 +13:00
if not os.path.exists(self.wine_binary) or not os.path.exists(
self.winepath_binary
2021-12-24 22:09:50 +13:00
):
# pylint: disable=E1136
self.signals.result_ready[str].emit(str())
return
2021-12-24 22:09:50 +13:00
path = self.path.strip().replace("/", "\\")
# lk: if path does not exist form
2021-12-24 22:09:50 +13:00
cmd = [self.wine_binary, "cmd", "/c", "echo", path]
# lk: if path exists and needs a case sensitive interpretation form
# cmd = [self.wine_binary, 'cmd', '/c', f'cd {path} & cd']
2021-12-24 22:09:50 +13:00
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=self.wine_env,
shell=False,
text=True,
)
out, err = proc.communicate()
# Clean wine output
out = out.strip().strip('"')
2021-12-24 22:09:50 +13:00
proc = subprocess.Popen(
[self.winepath_binary, "-u", out],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=self.wine_env,
shell=False,
text=True,
)
out, err = proc.communicate()
real_path = os.path.realpath(out.strip())
# pylint: disable=E1136
self.signals.result_ready[str].emit(real_path)
return
class CloudSignals(QObject):
result_ready = pyqtSignal(list) # List[SaveGameFile]
class CloudWorker(QRunnable):
def __init__(self):
super(CloudWorker, self).__init__()
self.signals = CloudSignals()
self.setAutoDelete(True)
self.core = LegendaryCoreSingleton()
def run(self) -> None:
try:
result = self.core.get_save_games()
2022-01-23 13:03:56 +13:00
except HTTPError:
result = None
self.signals.result_ready.emit(result)
2021-11-11 07:00:15 +13:00
def get_raw_save_path(game: Game):
if game.supports_cloud_saves:
2021-12-24 22:09:50 +13:00
return (
game.metadata.get("customAttributes", {})
.get("CloudSaveFolder", {})
.get("value")
2021-12-24 22:09:50 +13:00
)
def get_default_platform(app_name):
api_results = ApiResultsSingleton()
if platform.system() != "Darwin" or app_name not in api_results.mac_games:
return "Windows"
else:
2021-12-10 09:59:07 +13:00
return "Mac"
def icon(icn_str: str, fallback: str = None, **kwargs):
try:
return qtawesome.icon(icn_str, **kwargs)
except Exception as e:
if not fallback:
logger.warning(f"{e} {icn_str}")
if fallback:
try:
return qtawesome.icon(fallback, **kwargs)
except Exception as e:
logger.error(str(e))
if kwargs.get("color"):
kwargs["color"] = "red"
return qtawesome.icon("ei.error", **kwargs)