Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c2be93e
project: limit Qt versions
loathingKernel Oct 11, 2025
f9f8e17
RareLauncher: try to emulate reaper if SteamGameId is in the environment
loathingKernel Oct 11, 2025
4a63d44
GameDetails: Update form
loathingKernel Oct 11, 2025
9ae8d47
StoreDetails: update form
loathingKernel Oct 11, 2025
a188975
RareCore: replace global logger with class member
loathingKernel Oct 11, 2025
4cbc6a0
RareException: Use a messagebox with detailed descriptions for the ex…
loathingKernel Oct 11, 2025
1441237
workflows: upload zsync for appimage
loathingKernel Oct 12, 2025
674f592
pyproject: use vdf from https://github.com/solsticegamestudios/vdf
loathingKernel Oct 18, 2025
3dc0467
freeze: exclude libqgtk3.so
loathingKernel Oct 18, 2025
7887137
RareStyle: increase scrollbar width/height
loathingKernel Oct 18, 2025
f283a2b
chore: clean-up some code related to the library
loathingKernel Oct 27, 2025
a74b5d8
misc: update flatpak requirements
loathingKernel Oct 27, 2025
2f57f35
ImageManager: use rotated tall image if wide image is missing
loathingKernel Oct 27, 2025
309415e
RareAppException: reset excepthook to the default value on delete
loathingKernel Oct 28, 2025
072c4ad
RareGameBase: promote to QObject again and move signals class out of it
loathingKernel Oct 28, 2025
7c94b58
GlobalSignals: promote container to QObject
loathingKernel Oct 28, 2025
bfc6663
RareGameSignals: delete signals when cleaning up
loathingKernel Oct 28, 2025
8f10bd9
RareCore: delete RareGames on exit
loathingKernel Oct 28, 2025
d9e6466
ImageManager: check for image updates in the same thread as the download
loathingKernel Oct 28, 2025
9b05977
chore: ruff check
loathingKernel Oct 28, 2025
91e040c
RarAppException: properly capture the exception dialog choice
loathingKernel Oct 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/_job_cx-freeze-appimage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,16 @@ jobs:
run: |
python3 freeze.py bdist_appimage
mv dist/Rare-*.AppImage Rare-${{ inputs.version }}.AppImage
mv dist/Rare-*.zsync Rare-${{ inputs.version }}.zsync

- name: Upload artifact
uses: actions/upload-artifact@v5
with:
name: Rare-${{ inputs.version }}.AppImage
path: Rare-${{ inputs.version }}.AppImage

- name: Upload artifact
uses: actions/upload-artifact@v5
with:
name: Rare-${{ inputs.version }}.zsync
path: Rare-${{ inputs.version }}.zsync
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ jobs:
with:
version: ${{ github.ref_name }}
file1: Rare-${{ needs.version.outputs.version }}.AppImage
file2: Rare-${{ needs.version.outputs.version }}.zsync

cx-freeze-dmg:
if: ${{ true }}
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ jobs:
with:
version: ${{ needs.version.outputs.version }}
file1: Rare-${{ needs.version.outputs.version }}.AppImage
file2: Rare-${{ needs.version.outputs.version }}.zsync

cx-freeze-dmg:
if: ${{ true }}
Expand Down
5 changes: 4 additions & 1 deletion freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
_license = "LICENSE"

build_exe_options = {
"bin_excludes": ["qpdf.dll", "libqpdf.so", "libqpdf.dylib"],
"bin_excludes": [
"qpdf.dll", "libqpdf.so", "libqpdf.dylib",
"libqgtk3.so",
],
"excludes": [
"tkinter",
"unittest",
Expand Down
7 changes: 5 additions & 2 deletions misc/requirements-flatpak.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
build
installer
setuptools-scm
requests < 3.0
QtAwesome
legendary-gl @ https://github.com/RareDevs/legendary/archive/4074375b789a4c2c87f72307bc9581900e34fa50.zip
legendary-gl @ git+https://github.com/RareDevs/legendary@4074375b789a4c2c87f72307bc9581900e34fa50
orjson
vdf
vdf @ git+https://github.com/solsticegamestudios/vdf@be1f7220238022f8b29fe747f0b643f280bfdb6e
pypresence
4 changes: 2 additions & 2 deletions misc/requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ build
installer
setuptools-scm
requests < 3.0
PySide6-Essentials >= 6.8.1, != 6.9.2
PySide6-Essentials >= 6.8.1, < 6.9.2
QtAwesome
legendary-gl @ https://github.com/RareDevs/legendary/archive/4074375b789a4c2c87f72307bc9581900e34fa50.zip
orjson
vdf
vdf @ https://github.com/solsticegamestudios/vdf/archive/be1f7220238022f8b29fe747f0b643f280bfdb6e.zip
pywin32 ; platform_system == "Windows"
pypresence

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ classifiers = [
requires-python = ">= 3.10"
dependencies = [
"requests < 3.0",
"PySide6-Essentials >= 6.8.1, != 6.9.2",
"PySide6-Essentials >= 6.8.1, < 6.9.2",
"QtAwesome",
"legendary-gl @ https://github.com/RareDevs/legendary/archive/4074375b789a4c2c87f72307bc9581900e34fa50.zip",
"orjson",
"vdf",
"vdf @ https://github.com/solsticegamestudios/vdf/archive/be1f7220238022f8b29fe747f0b643f280bfdb6e.zip",
"pywin32 ; platform_system == 'Windows'",
]

Expand Down
78 changes: 43 additions & 35 deletions rare/commands/launcher/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import os
import platform
import shlex
import subprocess
Expand Down Expand Up @@ -36,7 +37,7 @@

from .cloud_sync_dialog import CloudSyncDialog, CloudSyncDialogResult
from .console_dialog import ConsoleDialog
from .lgd_helper import InitArgs, LaunchArgs, dict_to_qprocenv, get_configured_qprocess, get_launch_args
from .lgd_helper import InitParams, LaunchParams, dict_to_qprocenv, get_configured_qprocess, get_launch_params

DETACHED_APP_NAMES = {
"0a2d9f6403244d12969e11da6713137b", # Fall Guys
Expand All @@ -51,12 +52,12 @@

class PreLaunch(QRunnable):
class Signals(QObject):
ready_to_launch = Signal(LaunchArgs)
ready_to_launch = Signal(LaunchParams)
pre_launch_command_started = Signal()
pre_launch_command_finished = Signal(int) # exit_code
error_occurred = Signal(str)

def __init__(self, args: InitArgs, rgame: RareGameSlim, sync_action=None):
def __init__(self, args: InitParams, rgame: RareGameSlim, sync_action=None):
super(PreLaunch, self).__init__()
self.signals = self.Signals()
self.logger = getLogger(type(self).__name__)
Expand All @@ -78,30 +79,30 @@ def run(self) -> None:
else:
return

def prepare_launch(self, args: InitArgs) -> Optional[LaunchArgs]:
def prepare_launch(self, args: InitParams) -> Optional[LaunchParams]:
try:
launch_args = get_launch_args(self.rgame, args)
launch = get_launch_params(self.rgame, args)
except Exception as e:
self.signals.error_occurred.emit(str(e))
return None
if not launch_args:
if not launch:
return None

if launch_args.pre_launch_command:
proc = get_configured_qprocess(shlex.split(launch_args.pre_launch_command), launch_args.environment)
if launch.pre_launch_command:
proc = get_configured_qprocess(shlex.split(launch.pre_launch_command), launch.environment)
proc.setProcessChannelMode(QProcess.ProcessChannelMode.MergedChannels)
proc.readyReadStandardOutput.connect(
lambda: self.logger.info(str(proc.readAllStandardOutput().data(), "utf-8", "ignore"))
)
self.signals.pre_launch_command_started.emit()
self.logger.info("Running pre-launch command %s, %s", proc.program(), proc.arguments())
if launch_args.pre_launch_wait:
if launch.pre_launch_wait:
proc.start()
self.logger.info("Waiting for pre-launch command to finish")
proc.waitForFinished(-1)
else:
proc.startDetached()
return launch_args
return launch


class SyncCheckWorker(QRunnable):
Expand Down Expand Up @@ -147,7 +148,7 @@ def _handler(self, exc_type, exc_value, exc_tb) -> bool:
class RareLauncher(RareApp):
exit_app = Signal()

def __init__(self, args: InitArgs):
def __init__(self, args: InitParams):
super(RareLauncher, self).__init__(args, f"{type(self).__name__}_{args.app_name}_{{0}}.log")
self.socket: Optional[QLocalSocket] = None
self.console: Optional[ConsoleDialog] = None
Expand Down Expand Up @@ -293,68 +294,75 @@ def on_exit(self, exit_code: int):
self.stop()

@Slot(object)
def launch_game(self, args: LaunchArgs):
def launch_game(self, params: LaunchParams):
# should never happen
if not args:
if not params:
self.stop()
return
if self.console:
self.console.set_env(args.environment)
self.console.set_env(params.environment)
self.start_time = time.time()

if self.args.dry_run:
self.logger.info("Dry run %s (%s)", self.rgame.app_title, self.rgame.app_name)
self.logger.info("Command: %s, %s", args.executable, " ".join(args.arguments))
self.logger.info("Command: %s, %s", params.executable, " ".join(params.arguments))
if self.console:
self.console.log(f"Dry run {self.rgame.app_title} ({self.rgame.app_name})")
self.console.log(f"{shlex.join((args.executable, *args.arguments))}")
self.console.log(f"{shlex.join((params.executable, *params.arguments))}")
self.console.accept_close = True
self.stop()
return

if platform.system() == "Windows" and args.is_origin_game:
if platform.system() == "Windows" and params.is_origin_game:
# executable is a protocol link (link2ea://launchgame/...)
QDesktopServices.openUrl(QUrl(args.executable))
QDesktopServices.openUrl(QUrl(params.executable))
self.stop() # stop because it is not a subprocess
return

self.logger.info("Starting %s (%s)", self.rgame.app_title, self.rgame.app_name)
self.logger.info("Command: %s, %s", args.executable, " ".join(args.arguments))
self.logger.debug("Working directory %s", args.working_directory)
self.logger.info("Command: %s, %s", params.executable, " ".join(params.arguments))
self.logger.debug("Working directory %s", params.working_directory)

if self.rgame.app_name in DETACHED_APP_NAMES and platform.system() == "Windows":
if self.console:
self.console.log(f"Launching {args.executable} as a detached process")
self.console.log(f"Launching {params.executable} as a detached process")
subprocess.Popen(
(args.executable, *args.arguments),
(params.executable, *params.arguments),
stdin=None,
stdout=None,
stderr=None,
cwd=args.working_directory,
env=args.environment,
cwd=params.working_directory,
env=params.environment,
shell=True,
creationflags=subprocess.DETACHED_PROCESS,
)
self.stop() # stop because we do not attach to the output
return

if platform.system() == "Linux":
if platform.system() in {"Linux", "FreeBSD"}:
cmd_line = get_rare_executable()
executable, arguments = cmd_line[0], cmd_line[1:]

workdir = []
if args.working_directory:
workdir = ["--workdir", args.working_directory]
if appid := os.environ.get("SteamGameId", False):
params.environment["SteamGameId"] = appid

self.game_process.setProgram(executable)
self.game_process.setArguments([*arguments, "subreaper", *workdir, args.executable, *args.arguments])
# TODO: Add "SteamLauch" and "AppId=xxxxxx" here for steamdeck/gamescope
self.game_process.setArguments(
[*arguments, "subreaper", "SteamLaunch", f"AppId={int(appid) >> 32}", "--", params.executable, *params.arguments]
)
else:
self.game_process.setProgram(args.executable)
self.game_process.setArguments(args.arguments)
if args.working_directory:
self.game_process.setWorkingDirectory(args.working_directory)
self.game_process.setProgram(params.executable)
self.game_process.setArguments(params.arguments)

if params.working_directory:
self.game_process.setWorkingDirectory(params.working_directory)

if self.args.debug and self.console:
self.console.log(str(self.game_process.program()))
self.console.log(str(self.game_process.arguments()))

self.game_process.setProcessEnvironment(dict_to_qprocenv(args.environment))
self.game_process.setProcessEnvironment(dict_to_qprocenv(params.environment))
# send start message after process started
self.game_process.started.connect(
lambda: self.send_message(
Expand Down Expand Up @@ -459,7 +467,7 @@ def stop(self):


def launcher(args: Namespace) -> int:
args = InitArgs.from_argparse(args)
args = InitParams.from_argparse(args)

QApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps, True)
Expand Down
4 changes: 2 additions & 2 deletions rare/commands/launcher/console_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,10 @@ def log(self, text: str):
def log_stdout(self, text: str):
self.console_edit.log(text)

def error(self, text):
def error(self, text: str):
self.console_edit.error(f"Rare: {text}")

def log_stderr(self, text):
def log_stderr(self, text: str):
self.console_edit.error(text)

def on_process_exit(self, app_title: str, status: Union[int, str]):
Expand Down
Loading
Loading