diff --git a/.claude/settings.json b/.claude/settings.json index 0cbda98..89bc596 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -41,7 +41,8 @@ "Bash(awk '/int REAPERAPI_LoadAPI/,0' /Users/p4piwabl0/Desktop/projects/virtual-controller-clean/extension-reaper-macos/vendor/reaper-sdk/sdk/reaper_plugin_functions.h)", "Bash(grep -n \"{NULL, NULL}\")", "Bash(ls -la \"/Applications/REAPER.app/Contents/MacOS/\" 2>&1 | head -5 *)", - "Read(//Applications/REAPER.app/Contents/MacOS/**)" + "Read(//Applications/REAPER.app/Contents/MacOS/**)", + "Bash(src/.venv/Scripts/python.exe -m PyInstaller --name VirtualController --onedir --windowed --distpath dist --workpath build --specpath . --add-data \"src/app-presets/presets.json;app-presets\" src/main.py)" ] } } diff --git a/.gitignore b/.gitignore index 8d5e8f8..84cdb91 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ venv/ +.venv/ __pycache__/ @@ -6,6 +7,10 @@ __pycache__/ .DS_Store +# PyInstaller output (app-desktop) +app-desktop/build/ +app-desktop/dist/ + # Xcode *.xcuserstate xcuserdata/ diff --git a/app-desktop/VirtualController.spec b/app-desktop/VirtualController.spec new file mode 100644 index 0000000..f6b2375 --- /dev/null +++ b/app-desktop/VirtualController.spec @@ -0,0 +1,44 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['src\\main.py'], + pathex=[], + binaries=[], + datas=[('src/app-presets/presets.json', 'app-presets')], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='VirtualController', + 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='VirtualController', +) diff --git a/app-presets/presets.json b/app-desktop/src/app-presets/presets.json similarity index 100% rename from app-presets/presets.json rename to app-desktop/src/app-presets/presets.json diff --git a/app-desktop-macos/colorswatchpopup.py b/app-desktop/src/colorswatchpopup.py similarity index 100% rename from app-desktop-macos/colorswatchpopup.py rename to app-desktop/src/colorswatchpopup.py diff --git a/app-desktop-macos/extension_sender.py b/app-desktop/src/extension_sender.py similarity index 100% rename from app-desktop-macos/extension_sender.py rename to app-desktop/src/extension_sender.py diff --git a/app-desktop-macos/faderwidget.py b/app-desktop/src/faderwidget.py similarity index 100% rename from app-desktop-macos/faderwidget.py rename to app-desktop/src/faderwidget.py diff --git a/app-desktop-macos/focusfaderwidget.py b/app-desktop/src/focusfaderwidget.py similarity index 100% rename from app-desktop-macos/focusfaderwidget.py rename to app-desktop/src/focusfaderwidget.py diff --git a/app-desktop-macos/focusfeedbackwidget.py b/app-desktop/src/focusfeedbackwidget.py similarity index 100% rename from app-desktop-macos/focusfeedbackwidget.py rename to app-desktop/src/focusfeedbackwidget.py diff --git a/app-desktop-macos/focustogglewidget.py b/app-desktop/src/focustogglewidget.py similarity index 100% rename from app-desktop-macos/focustogglewidget.py rename to app-desktop/src/focustogglewidget.py diff --git a/app-desktop-macos/main.py b/app-desktop/src/main.py similarity index 100% rename from app-desktop-macos/main.py rename to app-desktop/src/main.py diff --git a/app-desktop-macos/midi_ports.py b/app-desktop/src/midi_ports.py similarity index 100% rename from app-desktop-macos/midi_ports.py rename to app-desktop/src/midi_ports.py diff --git a/app-desktop-macos/midi_receiver.py b/app-desktop/src/midi_receiver.py similarity index 100% rename from app-desktop-macos/midi_receiver.py rename to app-desktop/src/midi_receiver.py diff --git a/app-desktop-macos/midi_sender.py b/app-desktop/src/midi_sender.py similarity index 100% rename from app-desktop-macos/midi_sender.py rename to app-desktop/src/midi_sender.py diff --git a/app-desktop-macos/osc_manager.py b/app-desktop/src/osc_manager.py similarity index 100% rename from app-desktop-macos/osc_manager.py rename to app-desktop/src/osc_manager.py diff --git a/app-desktop-macos/osc_receiver.py b/app-desktop/src/osc_receiver.py similarity index 100% rename from app-desktop-macos/osc_receiver.py rename to app-desktop/src/osc_receiver.py diff --git a/app-desktop-macos/osc_sender.py b/app-desktop/src/osc_sender.py similarity index 100% rename from app-desktop-macos/osc_sender.py rename to app-desktop/src/osc_sender.py diff --git a/app-desktop-macos/palette.py b/app-desktop/src/palette.py similarity index 100% rename from app-desktop-macos/palette.py rename to app-desktop/src/palette.py diff --git a/app-desktop-macos/presets.py b/app-desktop/src/presets.py similarity index 73% rename from app-desktop-macos/presets.py rename to app-desktop/src/presets.py index 0c7d8bc..5ee2c0c 100644 --- a/app-desktop-macos/presets.py +++ b/app-desktop/src/presets.py @@ -9,10 +9,29 @@ from PyQt6.QtWidgets import ( ) -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -PRESETS_DIR = os.path.join(BASE_DIR, "app-presets") -PRESETS_FILE = os.path.join(PRESETS_DIR, "presets.json") -os.makedirs(PRESETS_DIR, exist_ok=True) +import shutil +import sys + +if getattr(sys, "frozen", False): + # Packaged exe: Program Files (or wherever it's installed) is not + # writable by a normal user, so presets live in the per-user AppData + # folder instead — standard Windows convention for app-written data. + PRESETS_DIR = os.path.join(os.environ["APPDATA"], "VirtualController") + PRESETS_FILE = os.path.join(PRESETS_DIR, "presets.json") + os.makedirs(PRESETS_DIR, exist_ok=True) + if not os.path.exists(PRESETS_FILE): + # First launch on this machine: seed from the presets.json bundled + # into the exe (sys._MEIPASS), so a fresh install starts with the + # real presets instead of empty. Only happens once — after this, + # AppData is the only copy that's ever read or written. + _bundled = os.path.join(sys._MEIPASS, "app-presets", "presets.json") + if os.path.exists(_bundled): + shutil.copyfile(_bundled, PRESETS_FILE) +else: + BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + PRESETS_DIR = os.path.join(BASE_DIR, "app-presets") + PRESETS_FILE = os.path.join(PRESETS_DIR, "presets.json") + os.makedirs(PRESETS_DIR, exist_ok=True) diff --git a/app-desktop-macos/presetwidget.py b/app-desktop/src/presetwidget.py similarity index 100% rename from app-desktop-macos/presetwidget.py rename to app-desktop/src/presetwidget.py diff --git a/app-desktop/src/run.py b/app-desktop/src/run.py new file mode 100644 index 0000000..6d71b93 --- /dev/null +++ b/app-desktop/src/run.py @@ -0,0 +1,5 @@ +import os +import runpy + +_here = os.path.dirname(os.path.abspath(__file__)) +runpy.run_path(os.path.join(_here, "main.py"), run_name="__main__") diff --git a/app-desktop-macos/styles.py b/app-desktop/src/styles.py similarity index 100% rename from app-desktop-macos/styles.py rename to app-desktop/src/styles.py diff --git a/app-desktop-macos/togglewidget.py b/app-desktop/src/togglewidget.py similarity index 100% rename from app-desktop-macos/togglewidget.py rename to app-desktop/src/togglewidget.py diff --git a/app-desktop-macos/transportwidget.py b/app-desktop/src/transportwidget.py similarity index 100% rename from app-desktop-macos/transportwidget.py rename to app-desktop/src/transportwidget.py diff --git a/remote-server/ws_server.py b/app-desktop/src/ws_server.py similarity index 100% rename from remote-server/ws_server.py rename to app-desktop/src/ws_server.py diff --git a/app-desktop-macos/zonebutton.py b/app-desktop/src/zonebutton.py similarity index 100% rename from app-desktop-macos/zonebutton.py rename to app-desktop/src/zonebutton.py diff --git a/remote-server/__init__.py b/remote-server/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/run.py b/run.py deleted file mode 100644 index 87e2395..0000000 --- a/run.py +++ /dev/null @@ -1,9 +0,0 @@ -import sys -import os - -_root = os.path.dirname(os.path.abspath(__file__)) -sys.path.insert(0, os.path.join(_root, "app-desktop-macos")) -sys.path.insert(0, os.path.join(_root, "remote-server")) - -import runpy -runpy.run_path(os.path.join(_root, "app-desktop-macos", "main.py"), run_name="__main__")