Roneat Studio Pro — Plugin Development
Plugin Architecture Overview
Plugins are isolated Python packages dropped into the app's plugins/ directory. The host app scans that folder on startup, validates plugin.json, dynamically loads the Python entry point, and routes lifecycle events via hooks.
The sandbox restricts os, shutil, subprocess, socket, urllib, requests, and http unless the matching permission is declared in plugin.json.
Plugin install path: %APPDATA%\RoneatStudioPro\plugins\ (runtime) or <project_root>/plugins/ (dev).
Plugin Structure
my-plugin/
├── plugin.json # Manifest (required)
├── __init__.py # Entry point (required)
└── assets/ # Optional: icons, data files
Distribute as my-plugin.zip — the zip must contain the folder (e.g. my-plugin/plugin.json).
plugin.json — Manifest Reference
{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"author": "Your Name",
"description": "Brief description of what this plugin does.",
"entry_point": "__init__.py",
"permissions": [],
"dependencies": [],
"hooks": {
"on_load": "on_load",
"on_app_start": "on_app_start",
"on_unload": "on_unload"
}
}
Required fields: id, name, version, author, entry_point.
For all fields and optional hooks (on_project_save, on_project_open), see references/manifest.md.
roneat_api — Python API
Import the injected virtual package at the top of your entry point. Do not install it via pip — it is injected at runtime by the host app.
import roneat_api.score as score
import roneat_api.audio as audio
import roneat_api.ui as ui
import roneat_api.project as project
import roneat_api.core as core
Key namespaces at a glance
| Namespace | Purpose |
|---|---|
roneat_api.score | Read/write the musical score text |
roneat_api.audio | Play notes/frequencies, stop playback |
roneat_api.ui | Inject tabs, buttons, dialogs, toasts; control theme/language |
roneat_api.project | Get project data, trigger save/load |
roneat_api.core | Read app settings |
For the full API reference with signatures and examples, see references/api.md.
Lifecycle Hooks
Hooks are Python functions in your entry point. Map them in plugin.json under "hooks".
| Hook | When it fires |
|---|---|
on_load | Plugin module loaded (app may not have UI yet) |
on_app_start | App window fully built; use this to inject UI |
on_unload | Plugin disabled/uninstalled |
on_project_save | User saves a project — receives data dict |
on_project_open | User opens a project — receives data dict |
# __init__.py — minimal lifecycle skeleton
import roneat_api.ui as ui
def on_load():
pass # Module is ready; avoid UI calls here
def on_app_start():
ui.show_toast("My Plugin loaded!", level="info")
def on_unload():
pass # Cleanup if needed
Injecting UI
All UI injection must happen inside on_app_start.
Add a Custom Tab
import customtkinter as ctk
import roneat_api.ui as ui
class MyTab(ctk.CTkFrame):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
ctk.CTkLabel(self, text="Hello from my plugin!").pack(pady=20)
def on_app_start():
ui.add_custom_tab(
tab_id="my_tab",
label="My Tab",
icon="🎵",
widget_class=MyTab
)
Open a Custom Window
def open_window():
win = ui.create_window("My Tool", width=500, height=400)
# win is a ctk.CTkToplevel; add widgets normally
Show Feedback
ui.show_toast("Done!", level="info") # Bottom toast
ui.show_dialog("Error", "Something failed", dialog_type="error")
Permissions Reference
Declare only the permissions your plugin actually uses:
| Permission | Unlocks |
|---|---|
"file_system" | os, shutil, pathlib, io |
"network" | socket, urllib, requests, http |
"system" | Implied by file_system; covers subprocess |
"permissions": ["file_system"]
Packaging & Installation
- Zip the plugin folder (not its contents directly):
my-plugin/ ├── plugin.json └── __init__.py → my-plugin.zip - Open Roneat Studio Pro → Plugins tab → Install Plugin.
- Select
my-plugin.zip. The plugin appears in the list immediately. - Enable/Disable/Reload/Uninstall from the same tab.
Full Example: Score Transpose Plugin
See references/example_transpose.md for a complete, annotated, production-quality plugin that reads the score, transposes all notes up by N steps, and re-writes the score — with a custom tab UI.
Common Pitfalls
- UI in
on_load→ Crashes. Always useon_app_startfor UI. - Importing restricted modules without permissions →
ImportErrorwith a security warning; plugin auto-disabled. - Long-running tasks in hooks → Use
threading.Thread(daemon=True)to avoid freezing the UI. - Assuming
roneat_apiis installable → It is not a PyPI package. It is injected by the host at runtime only. - Missing required manifest fields → Plugin silently fails to load.