Creating Parser Plugins
Parser plugins teach Joystick Diagrams how to read a new game's config files and return structured binding data.
A plugin reads game configuration, parses the bindings, and returns a ProfileCollection that the app
turns into diagrams.
Quick Start
Here's a minimal parser plugin. This is all you need to get started:
from pathlib import Path
from pydantic import Field
from joystick_diagrams.input.profile_collection import ProfileCollection
from joystick_diagrams.plugins.plugin_interface import PluginInterface
from joystick_diagrams.plugins.plugin_settings import PluginMeta, PluginSettings
class MySettings(PluginSettings):
source_dir: Path | None = Field(
default=None,
title="Source Folder",
json_schema_extra={"is_folder": True, "default_path": "~/Saved Games"},
)
class ParserPlugin(PluginInterface):
plugin_meta = PluginMeta(name="My Plugin", version="1.0.0", icon_path="img/icon.ico")
plugin_settings_model = MySettings
def process(self) -> ProfileCollection:
source = self.get_setting("source_dir")
# Parse your game's config files here
return ProfileCollection()
def on_settings_loaded(self) -> None:
# Rebuild internal state after settings are restored from disk
pass Plugin Structure
Place your plugin in the plugins/ directory:
plugins/
└── my_plugin/
├── __init__.py
├── main.py
└── img/
└── icon.ico
The main.py file must contain a ParserPlugin class that inherits from PluginInterface.
PluginMeta
Every plugin declares its identity via a PluginMeta instance:
| Field | Type | Description |
|---|---|---|
name | str | Display name shown in the UI |
version | str | Plugin version string |
icon_path | str | Path to icon, relative to the plugin directory |
Plugin Settings
Define a Pydantic BaseModel subclass of PluginSettings. The framework auto-generates
UI controls from your fields, persists values to disk, and computes the plugin's ready state.
Supported Field Types
| Field Type | UI Control |
|---|---|
bool | Checkbox |
str | Text input |
Path | Folder/file browse button (required) |
Path | None | Folder/file browse button (optional) |
Path Field Options
Configure path fields via json_schema_extra:
| Key | Type | Default | Description |
|---|---|---|---|
is_folder | bool | True | Folder dialog vs file dialog |
default_path | str | none | Starting directory for the browse dialog |
extensions | list[str] | none | Allowed file extensions (e.g. [".xml"]) |
required | bool | True | Plugin not ready until this path is set |
Ready State
A plugin is automatically considered ready when every required Path field has a value.
No manual implementation needed; the framework computes this from your settings model.
Lifecycle
| Method | Description |
|---|---|
process() | Called by the framework to get results. Must return a ProfileCollection. |
on_settings_loaded() | Called after settings are restored from disk. Override to rebuild internal state. |
save_plugin_state() | Handled by the framework. Don't call directly. |
load_settings() | Handled by the framework. Don't call directly. |
Settings Access
self.get_setting("field_name") Returns the current value of a settings field.
self.update_setting("field_name", value) Updates a settings field and persists the change to disk.
Plugin Data Directory
self.get_plugin_data_path() Returns the full path to the plugin's AppData directory.
self.get_plugin_data() Returns all files and folders in the plugin's AppData directory.
Exception Helpers
Raise these from process() when path validation fails:
self.file_not_valid_exception("msg") Raises FileNotValidError.
self.directory_not_valid_exception("msg") Raises DirectoryNotValidError.
self.file_type_invalid("msg") Raises FileTypeInvalidError.
Working with the Input Library
Your process() method returns a ProfileCollection. Here's how to build one:
from joystick_diagrams.input.profile_collection import ProfileCollection
from joystick_diagrams.input.button import Button
from joystick_diagrams.input.axis import Axis, AxisDirection
from joystick_diagrams.input.hat import Hat, HatDirection
from joystick_diagrams.input.axis_slider import AxisSlider
# Create a ProfileCollection (what your plugin returns)
collection = ProfileCollection()
# Create a profile (e.g. one per aircraft in DCS)
profile = collection.create_profile("A-10C II")
# Add a device (GUID, display name); returns the device object
device = profile.add_device("150D55D0-D984-11EE-8001-444553540000", "VPC Stick MT-50CM2")
# Add inputs to the device
device.create_input(Button(1), "Weapon Release")
device.create_input(Hat(1, HatDirection.D), "Trim Down")
device.create_input(Axis(AxisDirection.X), "Roll")
device.create_input(AxisSlider(1), "Zoom")
# Add a modifier (the library creates the base input if needed)
device.add_modifier_to_input(Button(1), {"Ctrl"}, "Pickle")
return collection Cross-Device Routing
If your plugin parses a tool that re-emits one device's inputs through another (a virtual joystick, a key remapper), you can declare routes so the downstream bindings surface on the physical device the user actually pressed. This is what ties DCS bindings back to a Virpil stick when Joystick Gremlin sits between them.
from joystick_diagrams.input_routing import RouteKey, RouteTarget, InputType
profile.add_route(
RouteKey(VJOY_GUID, InputType.BUTTON, 108),
RouteTarget(PHYSICAL_GUID, InputType.BUTTON, 3, qualifier="Short"),
) See the Input Routing API page for the full reference, qualifier semantics, and how the framework merges routes across profiles.
Distribution
Parser plugins ship as .zip files and install through the same flow as output plugins.
The zip must contain a single folder at the root with __init__.py and
main.py (which must define a ParserPlugin class subclassing
PluginInterface). An optional plugin.sig file enables the signed
& verified badge. See the
signing notes on the Output Plugins page;
the flow is identical for parser plugins.
Constraints
References
- A working example plugin is included in the repository at
joystick_diagrams/plugins/Example/example_plugin/ - Input Library Reference: full data model documentation
- GitHub Repository: browse the source
- Discord Server: get help and share plugins