A local web app for a Mac Studio that controls ProPresenter through the ProPresenter public REST API. It does not require Docker or npm packages. It now runs as a Flask application.
- Shows a selector for presentations found in ProPresenter libraries.
- Triggers the selected presentation at slide 1 when selected.
- Provides Previous and Next buttons, including a thumb-friendly sticky mobile control bar.
- Caches the selected presentation's slides, notes, and thumbnails in the browser so notes can keep advancing locally if ProPresenter becomes unreachable.
- Supports keyboard navigation with Arrow Left, Arrow Right, Page Up, and Page Down, plus swipe navigation on touch screens.
- Shows slide notes when your ProPresenter API exposes notes in the slide/status/presentation payloads.
- Adapts to phones and tablets with larger tap targets, responsive slide thumbnails, and simplified small-screen layout.
- Runs locally on the Mac Studio as a Flask web application.
- Can install as a macOS system service using
launchd.
- macOS on the Mac Studio.
- Python 3 available as
python3. - ProPresenter running with its public/network API enabled.
- Administrator access for the system service installer.
Docker, Node, and npm are not needed for the macOS service path. The startup scripts create a local Python virtual environment and install the Python dependencies from requirements.txt. Docker is also available as an optional deployment path.
In ProPresenter, open Settings > Network and enable the public/API network controls. Note the API port shown there. Common ProPresenter API ports include 1025, but you should use the value shown on your machine.
Before installing the service, edit config.json in this folder:
{
"app_host": "127.0.0.1",
"app_port": 3000,
"propresenter_scheme": "http",
"propresenter_host": "127.0.0.1",
"propresenter_port": 1025,
"poll_timeout_ms": 2500,
"ui_pin": ""
}Use 127.0.0.1 when ProPresenter is running on the same Mac Studio.
Use the ProPresenter computer's LAN IP address when ProPresenter is running on another computer, for example:
"propresenter_host": "192.168.1.50"Set ui_pin in config.json to require a PIN before anyone can open the controller UI or call its API endpoints:
"ui_pin": "1234"Leave ui_pin empty to disable the lock screen. You can also set the UI_PIN environment variable, which takes precedence over config.json. After changing the PIN, restart the app or service.
Double-click:
Install Service.command
The installer will ask for your Mac administrator password because it writes a LaunchDaemon to:
/Library/LaunchDaemons/org.propresenter.notescontroller.plist
It installs the app to:
/Library/Application Support/ProPresenterNotesController
It writes logs to:
/Library/Logs/ProPresenterNotesController/out.log
/Library/Logs/ProPresenterNotesController/err.log
After installation, open:
http://127.0.0.1:3000
The service starts at boot and restarts automatically if it exits.
Edit this installed config file:
/Library/Application Support/ProPresenterNotesController/config.json
Then restart the service:
sudo launchctl kickstart -k system/org.propresenter.notescontrollerOr rerun Install Service.command; it preserves the installed config file.
Double-click:
Service Status.command
Or run:
sudo launchctl print system/org.propresenter.notescontrollerDouble-click:
Uninstall Service.command
This removes the LaunchDaemon and stops the service. It intentionally leaves the installed app folder and logs in place so your config is not deleted. The uninstall script prints the command to remove those files too.
Double-click:
Start.command
Then open:
http://127.0.0.1:3000
Run the Python unit test suite locally with:
python -m unittest discover -s tests -vRun the same test suite in Docker with the test build target used by CI:
docker build --target test -t propresenter-notes:test .
docker run --rm propresenter-notes:testThe test suite includes a local mock ProPresenter API server backed by the captured OpenAPI example payloads in tests/propresenter_openapi_examples.json. Those integration tests exercise the real ProPresenterClient over HTTP for /version, /v1/libraries, and /v1/library/{library_id} without requiring a running ProPresenter instance. The checked-in Swagger export at tests/swagger.json is also parsed by contract tests so app-generated ProPresenter requests and representative fixture examples stay aligned with the documented API paths. As more Swagger examples are collected, add them to that fixture and extend tests/mock_propresenter_api.py so Docker and CI continue validating against the documented API contract.
To capture examples from a real ProPresenter system for review, run:
python scripts/capture_propresenter_examples.py --base-url http://127.0.0.1:1025 --output propresenter_examples_capture.jsonThe capture script reads /version, /v1/libraries, /v1/library/{library_id} using UUID/name/index forms, selected presentation details, the first slide thumbnail, and slide-status endpoints. It intentionally skips trigger endpoints because they mutate the live ProPresenter state. Review the generated file for sensitive presentation text before sharing it or copying examples into tests/propresenter_openapi_examples.json.
GitHub Actions runs the Dockerized test suite on pushes to main, pull requests, and manual workflow dispatches.
Docker is an optional deployment path; the macOS service scripts above remain supported. Build the image from this folder with:
docker build -t propresenter-notes .When ProPresenter is running on the same Mac as Docker, run the container with PROPRESENTER_HOST=host.docker.internal and set PROPRESENTER_PORT to the API port shown in Settings > Network:
docker run --rm -p 3000:3000 -e APP_HOST=0.0.0.0 -e PROPRESENTER_HOST=host.docker.internal -e PROPRESENTER_PORT=<port> propresenter-notesThen open the web UI at:
http://127.0.0.1:3000
When ProPresenter is running on another machine, use that machine's LAN IP address for PROPRESENTER_HOST and set PROPRESENTER_PORT to its API port. For example:
docker run --rm -p 3000:3000 -e APP_HOST=0.0.0.0 -e PROPRESENTER_HOST=192.168.1.50 -e PROPRESENTER_PORT=<port> propresenter-notesInside Docker, 127.0.0.1 refers to the container itself, not the Mac host. Use host.docker.internal for ProPresenter on the same Mac, or the ProPresenter machine's LAN IP address for ProPresenter on another computer.
If you prefer Docker Compose, you can also run:
docker compose up --buildThe compose file exposes the web UI on http://127.0.0.1:3000.
Control-click the .command file and choose Open. You may need to approve it in System Settings > Privacy & Security.
Change app_port in config.json, then restart the app or service.
- Confirm ProPresenter is open.
- Confirm the ProPresenter public/network API is enabled.
- Confirm
propresenter_hostandpropresenter_portmatch the ProPresenter machine. - Confirm both devices are on the same network if ProPresenter is not on the same Mac.
- If the selected presentation was cached before the outage, the UI enters offline mode and keeps the notes moving locally when you press Previous, Next, keyboard shortcuts, swipe, or tap thumbnails. ProPresenter itself will not change slides again until its API connection is restored.
Different ProPresenter versions expose note text differently through the public API. This app scans common fields such as notes, slideNotes, speakerNotes, presenterNotes, and stageNotes. If your version exposes notes under a different field or endpoint, update the parsing helpers in propresenter_notes/services.py.


