A Settings Plugin is an optional settings.dll loaded by the OpenSR host when the user opens the Settings tab of a plugin inside the Plugin Manager.
Unlike settings.xml, which uses the built-in XML editor, a settings.dll gives full control over rendering, interactions, validation, animations, and workflow.
A Settings Plugin can use any rendering technology:
- Win32 GDI
- Direct2D
- DirectWrite
- OpenGL
- Vulkan
- ImGui
- Custom software renderer
The example below uses:
- Direct2D for rendering
- DirectWrite for text rendering
- TinyXML2 for settings persistence
The BeamNG Drive example demonstrates:
- Editable UDP IP/Port fields
- Boolean toggle switches
- Clipboard support
- Caret and text selection handling
- Validation logic
- Direct2D custom controls
- XML settings persistence
- Parent notification through
_PLUGIN_SETTINGS_CHANGED
Although the original example comes from a Game (IN) plugin, the implementation is identical for OUT plugins.
Settings Plugin Lifecycle
Unlike normal plugins, a Settings Plugin only exists while its settings page is visible.
Typical lifecycle:
- User opens Plugin Manager
- User selects a plugin
- User clicks the Settings tab
- Host loads
settings.dll - Host creates the settings object
- Host provides a child HWND
- Plugin renders its UI
- User edits/saves settings
- User closes the page
- Host destroys the settings object
- DLL can be unloaded
Settings plugins are therefore:
- Short-lived
- UI-focused
- Event-driven
- Usually single-threaded
Unlike Game or OUT plugins:
- Lifetime is strictly tied to the visible settings page
- Settings plugins do NOT run permanent worker threads
- Settings plugins are temporary UI modules
Folder Structure
Example:
Plugins/
+-- BeamNGDrive/
+-- BeamNGDrive.osrp
+-- settings.dll
+-- settings.xml
The presence of settings.dll tells the host to use the custom settings UI instead of the generic XML editor.
Required Interface
A Settings Plugin must implement IPluginSettings.
Example:
class BeamNGDriveSettings : public IPluginSettings
{
public:
BOOL Initialize(
HWND hWindow,
OpenSRContext* context,
void* buffersOut,
const wchar_t* pluginFolderPath
) override;
BOOL Shutdown() override;
BOOL WantsDialogMessages() const override
{
return FALSE;
}
};Exported Functions
The DLL must export two C functions:
extern "C" __declspec(dllexport)
IPluginSettings* CreatePluginSettings();
extern "C" __declspec(dllexport)
void DestroyPluginSettings(IPluginSettings* pSettings);Example:
extern "C" __declspec(dllexport)
IPluginSettings* CreatePluginSettings()
{
return new BeamNGDriveSettings();
}
extern "C" __declspec(dllexport)
void DestroyPluginSettings(IPluginSettings* pSettings)
{
if (pSettings)
{
delete static_cast<BeamNGDriveSettings*>(pSettings);
}
}Initialize()
The host provides:
| Parameter | Description |
|---|---|
hWindow | Child window owned by the dashboard |
context | Shared OpenSR context |
buffersOut | Optional output buffers |
pluginFolderPath | Absolute plugin folder path |
Example:
BOOL BeamNGDriveSettings::Initialize(
HWND hWindow,
OpenSRContext* context,
void* buffersOut,
const wchar_t* pluginFolderPath)
{
m_hWnd = hWindow;
m_pContext = context;
m_pluginFolderPath = pluginFolderPath;
SetWindowSubclass(
m_hWnd,
BeamNGDriveSettings::SubclassWndProc,
1,
(DWORD_PTR)this);
LoadXMLSettings();
return TRUE;
}The provided window is owned by the host.
The settings plugin should:
- Subclass the window
- Render inside this window only
- Never destroy the window itself
- Never modify parent ownership
Shutdown()
Shutdown is called when the settings page closes.
Typical responsibilities:
- Remove subclass
- Release Direct2D resources
- Release DirectWrite resources
- Destroy temporary objects
Example:
BOOL BeamNGDriveSettings::Shutdown()
{
if (m_hWnd && IsWindow(m_hWnd))
{
RemoveWindowSubclass(
m_hWnd,
BeamNGDriveSettings::SubclassWndProc,
1);
}
SafeRelease(&m_pTextFormat);
SafeRelease(&m_pRenderTarget);
SafeRelease(&m_pDWriteFactory);
SafeRelease(&m_pD2DFactory);
return m_touched;
}The settings plugin must release:
- Direct2D resources
- DirectWrite resources
- Timers
- Window subclass hooks
- Dynamic allocations
The plugin must NOT:
- Destroy the host window
- Destroy the parent window
- Destroy OpenSR-owned resources
Direct2D Rendering
Settings plugins are free to use:
- Direct2D
- DirectWrite
- GDI
- OpenGL
- Vulkan
- Custom software rendering
The BeamNG example uses Direct2D:
ID2D1FactoryIDWriteFactoryID2D1HwndRenderTarget
Example:
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
&m_pD2DFactory);
DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(&m_pDWriteFactory));Rendering is performed inside WM_PAINT.
Window Subclassing
The example subclasses the provided host child window:
SetWindowSubclass(
m_hWnd,
BeamNGDriveSettings::SubclassWndProc,
1,
(DWORD_PTR)this);This allows the settings page to process:
- Mouse input
- Keyboard input
- Painting
- Resize events
- Timers
Typical messages:
WM_PAINT
WM_LBUTTONDOWN
WM_MOUSEMOVE
WM_KEYDOWN
WM_CHAR
WM_SIZE
WM_TIMERExample dispatcher:
LRESULT CALLBACK BeamNGDriveSettings::SubclassWndProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam,
UINT_PTR uIdSubclass,
DWORD_PTR dwRefData)
{
BeamNGDriveSettings* pThis =
(BeamNGDriveSettings*)dwRefData;
switch (uMsg)
{
case WM_PAINT:
pThis->OnPaint();
return 0;
}
return DefSubclassProc(
hWnd,
uMsg,
wParam,
lParam);
}
Managing Settings
The BeamNG example manages six settings:
<settings type="unique">
<option name="outgauge ip" type="text">127.0.0.1</option>
<option name="outgauge port" type="number">4441</option>
<option name="motionsim ip" type="text">127.0.0.1</option>
<option name="motionsim port" type="number">4444</option>
<option name="outgauge allowed" type="bool">false</option>
<option name="motionsim allowed" type="bool">true</option>
</settings>The implementation therefore manages:
- Two UDP IP addresses
- Two UDP ports
- Two boolean enable/disable toggles
The XML file is loaded during Initialize().
Loading XML Settings
Example:
tinyxml2::XMLDocument doc;
doc.LoadFile(pathObj.c_str());
tinyxml2::XMLElement* root =
doc.FirstChildElement("settings");Each <option> is parsed and copied into internal UI fields.
Saving XML Settings
The plugin validates all fields before saving.
Example:
for (auto& field : m_inputs)
{
field.Validate();
if (!field.isValid)
allValid = false;
}Then values are written back to XML:
opt->SetText(valStr.c_str());
Finally:
doc.SaveFile(pathObj.c_str());
Input Validation
The example contains:
- IP validation
- Partial IP validation during typing
- Numeric clamping
- Port range validation
Example:
TYPE_NUMBER, 1024, 65535
The settings UI visually highlights invalid fields using a red border.
Clipboard Support
The example implements:
- CTRL+C
- CTRL+V
- CTRL+X
- CTRL+A
Using standard Win32 clipboard APIs:
OpenClipboard()
GetClipboardData()
SetClipboardData()Caret and Text Selection
The example uses DirectWrite hit testing:
pLayout->HitTestPoint(...)This enables:
- Mouse caret placement
- Drag selection
- Keyboard navigation
- Accurate text measurement
The settings page therefore behaves similarly to a native text editor.
Toggle Controls
The example also implements custom Direct2D toggle switches.
Example managed values:
bool m_outgaugeAllowed = true;
bool m_motionSimAllowed = true;Rendering is performed manually using:
- Rounded rectangles
- Ellipses
- Custom colors
Toast Notifications
The example contains a lightweight toast system.
Example:
ShowToast(L"Settings Saved Successfully");Used for:
- Save confirmation
- Validation errors
- User feedback
Notifying the Parent Plugin
After saving settings, it is strongly recommended to notify the parent plugin window.
Example:
HWND hParent = GetParent(m_hWnd);
if (hParent && IsWindow(hParent))
{
PostMessage(
hParent,
_PLUGIN_SETTINGS_CHANGED,
0,
0);
}This allows the parent plugin to:
- Reload configuration
- Reopen sockets
- Refresh internal state
- Rebuild outputs
- Apply runtime changes immediately
This mechanism is especially important for:
- UDP plugins
- Serial devices
- HID devices
- Motion systems
- Network outputs
Important Notes
Settings plugins should:
- Avoid heavy background processing
- Avoid long blocking operations
- Release all Direct2D resources correctly
- Never assume permanent existence
- Treat the provided HWND as host-owned
Recommended behavior:
- Load settings during
Initialize() - Save only on explicit user action
- Notify parent after successful save
- Keep UI responsive
Recommended Architecture
A good Settings Plugin architecture usually separates:
- Rendering
- Input handling
- XML persistence
- Validation
- UI widgets
The BeamNG example demonstrates a clean custom-control approach using:
InputField- Toggle controls
- Subclassed message handling
- Manual Direct2D rendering
This architecture scales well for:
- Complex plugin settings
- Device calibration tools
- Motion tuning panels
- Complex network configuration pages
- Advanced configuration panels
- Device setup pages (shifter, pedals set calibration)
- Real-time network configuration
- Visual editors
- Direct2D/DirectWrite custom interfaces
- Hardware-specific utilities (Firmware update)
