From 480267cef54376821a8c1ad6f7a45d79045f9872 Mon Sep 17 00:00:00 2001 From: vlad ciotescu Date: Wed, 13 May 2026 18:53:34 +0300 Subject: [PATCH 1/3] Fix mac app sound resource paths --- clients/desktop/PlayPalace.spec | 59 +++++++++++++++++++++ clients/desktop/pyinstaller_runtime.py | 8 +++ clients/desktop/sound_manager.py | 11 +++- clients/desktop/tests/test_sound_manager.py | 7 +++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 clients/desktop/PlayPalace.spec create mode 100644 clients/desktop/pyinstaller_runtime.py diff --git a/clients/desktop/PlayPalace.spec b/clients/desktop/PlayPalace.spec new file mode 100644 index 00000000..e3162def --- /dev/null +++ b/clients/desktop/PlayPalace.spec @@ -0,0 +1,59 @@ +# -*- mode: python ; coding: utf-8 -*- + +import shutil +from pathlib import Path + + +a = Analysis( + ['client.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=['pyinstaller_runtime.py'], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='PlayPalace', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) +coll = COLLECT( + exe, + a.binaries, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='PlayPalace', +) +app = BUNDLE( + coll, + name='PlayPalace.app', + icon=None, + bundle_identifier=None, +) + +source_sounds = Path('sounds') +bundle_sounds = Path(DISTPATH) / 'PlayPalace.app' / 'Contents' / 'MacOS' / 'sounds' +if source_sounds.exists(): + shutil.rmtree(bundle_sounds, ignore_errors=True) + shutil.copytree(source_sounds, bundle_sounds) diff --git a/clients/desktop/pyinstaller_runtime.py b/clients/desktop/pyinstaller_runtime.py new file mode 100644 index 00000000..fe30c91a --- /dev/null +++ b/clients/desktop/pyinstaller_runtime.py @@ -0,0 +1,8 @@ +"""Runtime setup for PyInstaller-built desktop bundles.""" + +import os +import sys + + +if getattr(sys, "frozen", False): + os.chdir(os.path.dirname(sys.executable)) diff --git a/clients/desktop/sound_manager.py b/clients/desktop/sound_manager.py index 1d89bac7..cd302805 100644 --- a/clients/desktop/sound_manager.py +++ b/clients/desktop/sound_manager.py @@ -2,13 +2,22 @@ import logging import os +import sys import threading import time +from pathlib import Path from sound_cacher import SoundCacher LOG = logging.getLogger(__name__) +def _default_sounds_folder() -> str: + """Return the bundled sounds folder for source and PyInstaller runs.""" + if getattr(sys, "frozen", False): + return str(Path(sys.executable).resolve().parent / "sounds") + return str(Path(__file__).resolve().parent / "sounds") + + class AudioPlaylist: """Represents a playlist that can play either sounds or music tracks.""" @@ -328,7 +337,7 @@ def __init__(self): self.current_music = None self.current_music_name = None self.music_volume = 0.2 - self.sounds_folder = "sounds" + self.sounds_folder = _default_sounds_folder() # Configurable menu sounds (can be changed by server) self.menuclick_sound = "menuclick.ogg" diff --git a/clients/desktop/tests/test_sound_manager.py b/clients/desktop/tests/test_sound_manager.py index 33d9c6b9..944211d4 100644 --- a/clients/desktop/tests/test_sound_manager.py +++ b/clients/desktop/tests/test_sound_manager.py @@ -41,6 +41,13 @@ def make_manager(tmp_path): return manager +def test_default_sounds_folder_is_absolute(): + manager = SoundManager() + + assert manager.sounds_folder.endswith("sounds") + assert manager.sounds_folder.startswith("/") + + def test_play_passes_full_path(tmp_path): (tmp_path / "click.ogg").write_bytes(b"123") manager = make_manager(tmp_path) From 8660db2f8fc46235942966b5140a48daf2ebb056 Mon Sep 17 00:00:00 2001 From: vlad ciotescu Date: Wed, 20 May 2026 13:51:34 +0300 Subject: [PATCH 2/3] Address mac sound PR feedback --- clients/desktop/PlayPalace.spec | 14 ++--------- clients/desktop/pyinstaller_runtime.py | 8 ------ clients/desktop/sound_manager.py | 3 +++ clients/desktop/tests/test_sound_manager.py | 27 ++++++++++++++++++++- 4 files changed, 31 insertions(+), 21 deletions(-) delete mode 100644 clients/desktop/pyinstaller_runtime.py diff --git a/clients/desktop/PlayPalace.spec b/clients/desktop/PlayPalace.spec index e3162def..f9ee271a 100644 --- a/clients/desktop/PlayPalace.spec +++ b/clients/desktop/PlayPalace.spec @@ -1,18 +1,14 @@ # -*- mode: python ; coding: utf-8 -*- -import shutil -from pathlib import Path - - a = Analysis( ['client.py'], pathex=[], binaries=[], - datas=[], + datas=[('sounds', 'sounds')], hiddenimports=[], hookspath=[], hooksconfig={}, - runtime_hooks=['pyinstaller_runtime.py'], + runtime_hooks=[], excludes=[], noarchive=False, optimize=0, @@ -51,9 +47,3 @@ app = BUNDLE( icon=None, bundle_identifier=None, ) - -source_sounds = Path('sounds') -bundle_sounds = Path(DISTPATH) / 'PlayPalace.app' / 'Contents' / 'MacOS' / 'sounds' -if source_sounds.exists(): - shutil.rmtree(bundle_sounds, ignore_errors=True) - shutil.copytree(source_sounds, bundle_sounds) diff --git a/clients/desktop/pyinstaller_runtime.py b/clients/desktop/pyinstaller_runtime.py deleted file mode 100644 index fe30c91a..00000000 --- a/clients/desktop/pyinstaller_runtime.py +++ /dev/null @@ -1,8 +0,0 @@ -"""Runtime setup for PyInstaller-built desktop bundles.""" - -import os -import sys - - -if getattr(sys, "frozen", False): - os.chdir(os.path.dirname(sys.executable)) diff --git a/clients/desktop/sound_manager.py b/clients/desktop/sound_manager.py index cac5fdec..1895ec92 100644 --- a/clients/desktop/sound_manager.py +++ b/clients/desktop/sound_manager.py @@ -14,6 +14,9 @@ def _default_sounds_folder() -> str: """Return the bundled sounds folder for source and PyInstaller runs.""" if getattr(sys, "frozen", False): + bundle_root = getattr(sys, "_MEIPASS", None) + if bundle_root: + return str(Path(bundle_root) / "sounds") return str(Path(sys.executable).resolve().parent / "sounds") return str(Path(__file__).resolve().parent / "sounds") diff --git a/clients/desktop/tests/test_sound_manager.py b/clients/desktop/tests/test_sound_manager.py index dba55515..0d77fc0e 100644 --- a/clients/desktop/tests/test_sound_manager.py +++ b/clients/desktop/tests/test_sound_manager.py @@ -1,3 +1,5 @@ +import os + import pytest import sound_manager as sm_mod @@ -45,7 +47,30 @@ def test_default_sounds_folder_is_absolute(): manager = SoundManager() assert manager.sounds_folder.endswith("sounds") - assert manager.sounds_folder.startswith("/") + assert os.path.isabs(manager.sounds_folder) + + +def test_default_sounds_folder_uses_pyinstaller_meipass(monkeypatch, tmp_path): + bundle_root = tmp_path / "bundle" + executable = tmp_path / "PlayPalace.app" / "Contents" / "MacOS" / "PlayPalace" + + monkeypatch.setattr(sm_mod.sys, "frozen", True, raising=False) + monkeypatch.setattr(sm_mod.sys, "_MEIPASS", str(bundle_root), raising=False) + monkeypatch.setattr(sm_mod.sys, "executable", str(executable)) + + assert sm_mod._default_sounds_folder() == str(bundle_root / "sounds") + + +def test_default_sounds_folder_uses_frozen_executable_without_meipass( + monkeypatch, tmp_path +): + executable = tmp_path / "PlayPalace.app" / "Contents" / "MacOS" / "PlayPalace" + + monkeypatch.setattr(sm_mod.sys, "frozen", True, raising=False) + monkeypatch.delattr(sm_mod.sys, "_MEIPASS", raising=False) + monkeypatch.setattr(sm_mod.sys, "executable", str(executable)) + + assert sm_mod._default_sounds_folder() == str(executable.parent / "sounds") def test_play_passes_full_path(tmp_path): From ebf54dafe9fd675b11219de417c35051bebb8b02 Mon Sep 17 00:00:00 2001 From: vlad ciotescu Date: Sun, 24 May 2026 21:32:21 +0300 Subject: [PATCH 3/3] Handle missing PyInstaller sounds directory --- clients/desktop/sound_manager.py | 4 +++- clients/desktop/tests/test_sound_manager.py | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/clients/desktop/sound_manager.py b/clients/desktop/sound_manager.py index 1895ec92..af9b9016 100644 --- a/clients/desktop/sound_manager.py +++ b/clients/desktop/sound_manager.py @@ -16,7 +16,9 @@ def _default_sounds_folder() -> str: if getattr(sys, "frozen", False): bundle_root = getattr(sys, "_MEIPASS", None) if bundle_root: - return str(Path(bundle_root) / "sounds") + candidate = Path(bundle_root) / "sounds" + if candidate.is_dir(): + return str(candidate) return str(Path(sys.executable).resolve().parent / "sounds") return str(Path(__file__).resolve().parent / "sounds") diff --git a/clients/desktop/tests/test_sound_manager.py b/clients/desktop/tests/test_sound_manager.py index 0d77fc0e..bb0c8995 100644 --- a/clients/desktop/tests/test_sound_manager.py +++ b/clients/desktop/tests/test_sound_manager.py @@ -52,6 +52,7 @@ def test_default_sounds_folder_is_absolute(): def test_default_sounds_folder_uses_pyinstaller_meipass(monkeypatch, tmp_path): bundle_root = tmp_path / "bundle" + (bundle_root / "sounds").mkdir(parents=True) executable = tmp_path / "PlayPalace.app" / "Contents" / "MacOS" / "PlayPalace" monkeypatch.setattr(sm_mod.sys, "frozen", True, raising=False) @@ -61,6 +62,20 @@ def test_default_sounds_folder_uses_pyinstaller_meipass(monkeypatch, tmp_path): assert sm_mod._default_sounds_folder() == str(bundle_root / "sounds") +def test_default_sounds_folder_falls_back_when_meipass_sounds_missing( + monkeypatch, tmp_path +): + bundle_root = tmp_path / "_internal" + bundle_root.mkdir() + executable = tmp_path / "PlayPalace" / "PlayPalace.exe" + + monkeypatch.setattr(sm_mod.sys, "frozen", True, raising=False) + monkeypatch.setattr(sm_mod.sys, "_MEIPASS", str(bundle_root), raising=False) + monkeypatch.setattr(sm_mod.sys, "executable", str(executable)) + + assert sm_mod._default_sounds_folder() == str(executable.parent / "sounds") + + def test_default_sounds_folder_uses_frozen_executable_without_meipass( monkeypatch, tmp_path ):