> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/opentrack/opentrack/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin Development

> Complete guide to developing custom trackers, filters, and protocols for OpenTrack

OpenTrack has a modular plugin architecture that allows you to extend functionality by creating custom **trackers**, **filters**, and **protocols**. All plugins use the same base API defined in `api/plugin-api.hpp`.

## Plugin Architecture Overview

### Plugin Types

OpenTrack supports three types of plugins:

<CardGroup cols={3}>
  <Card title="Trackers" icon="video">
    Read input from hardware devices or algorithms to generate head pose data (XYZ position + Yaw, Pitch, Roll)
  </Card>

  <Card title="Filters" icon="filter">
    Process and smooth raw tracking data to reduce noise and improve responsiveness
  </Card>

  <Card title="Protocols" icon="network-wired">
    Send processed tracking data to games and simulators using various output methods
  </Card>
</CardGroup>

### Pose Data Structure

All plugins work with a 6-DOF pose:

```cpp theme={null}
using Pose = Mat<double, 6, 1>;

enum Axis : int
{
    NonAxis = -1,
    TX = 0, TY = 1, TZ = 2,        // Translation X, Y, Z (position)
    Yaw = 3, Pitch = 4, Roll = 5,  // Rotation (orientation)
    Axis_MIN = TX, Axis_MAX = 5,
    Axis_COUNT = 6,
};
```

## Setting Up Your Plugin

<Steps>
  <Step title="Create Plugin Directory">
    Create a directory for your plugin in the OpenTrack source tree:

    ```bash theme={null}
    cd opentrack
    mkdir tracker-myplugin  # or filter-myplugin, proto-myplugin
    cd tracker-myplugin
    ```
  </Step>

  <Step title="Create CMakeLists.txt">
    Create a minimal `CMakeLists.txt`:

    ```cmake theme={null}
    otr_module(tracker-myplugin)
    ```

    The `otr_module()` function automatically:

    * Finds all `.cpp`, `.h`, `.hpp`, `.ui`, and `.qrc` files
    * Links against Qt and OpenTrack APIs
    * Sets up installation rules
    * Configures translations
  </Step>

  <Step title="Create Plugin Files">
    Create header and implementation files:

    * `myplugin.h` - Class declarations
    * `myplugin.cpp` - Implementation
    * `myplugin_dialog.cpp` - UI dialog (optional)
    * `myplugin.ui` - Qt Designer UI file (optional)
  </Step>

  <Step title="Link Against OpenTrack API">
    **Important:** You must link against `opentrack-api` in CMakeLists.txt to avoid vtable link errors:

    ```cmake theme={null}
    otr_module(tracker-myplugin)
    target_link_libraries(opentrack-tracker-myplugin PRIVATE opentrack-api)
    ```
  </Step>
</Steps>

## Developing a Tracker Plugin

### Tracker Interface

Implement the `ITracker` interface:

```cpp theme={null}
struct OTR_API_EXPORT ITracker
{
    ITracker();
    virtual ~ITracker();
    
    // Start tracking, optionally use frame for video display
    virtual module_status start_tracker(QFrame* frame) = 0;
    
    // Return XYZ yaw pitch roll data
    // Don't block here - use a separate thread for computation
    virtual void data(double *data) = 0;
    
    // Called when user centers tracking
    // Return true to make identity the center pose
    virtual bool center();
    
    static module_status status_ok();
    static module_status error(const QString& error);
};
```

### Complete Tracker Example

Here's a complete working tracker that generates sinusoidal test data:

<CodeGroup>
  ```cpp myplugin.h theme={null}
  #pragma once
  #include "api/plugin-api.hpp"
  #include "compat/timer.hpp"
  #include <cmath>

  class my_tracker : public ITracker
  {
  public:
      my_tracker();
      ~my_tracker() override;
      module_status start_tracker(QFrame *) override;
      void data(double *data) override;

  private:
      double last[6] {};
      Timer t;
  };

  class my_dialog : public ITrackerDialog
  {
      Q_OBJECT
  public:
      my_dialog();
      void register_tracker(ITracker *) override {}
      void unregister_tracker() override {}
  };

  class my_metadata : public Metadata
  {
      Q_OBJECT
      
      QString name() override { return tr("My Tracker"); }
      QIcon icon() override { return QIcon(":/images/icon.png"); }
  };
  ```

  ```cpp myplugin.cpp theme={null}
  #include "myplugin.h"
  #include "api/plugin-api.hpp"

  my_tracker::my_tracker() = default;
  my_tracker::~my_tracker() = default;

  module_status my_tracker::start_tracker(QFrame*)
  {
      // Initialize your tracker here
      // Return error("Failed to initialize") on failure
      t.start();
      return status_ok();
  }

  void my_tracker::data(double *data)
  {
      // This is called 250 times per second
      // Don't block here - use a separate thread for heavy computation
      
      const double dt = t.elapsed_seconds();
      t.start();
      
      // Generate sample data
      static const double incr[3] = { 10, 5, 3 };
      static const double max_values[3] = { 180, 2, 3 };
      
      for (int i = 0; i < 3; i++)
      {
          double last_ = last[i];
          double max = max_values[i] * 2;
          double incr_ = incr[i];
          double x = fmod(last_ + incr_ * dt, max);
          last[i] = x;
          if (x > max_values[i])
              x = -max + x;
          data[i+3] = x;  // Rotation (Yaw, Pitch, Roll)
      }
      
      // Translation data
      data[0] = 0;  // TX
      data[1] = 0;  // TY
      data[2] = 0;  // TZ
  }

  // Declare the plugin - this macro is required
  OPENTRACK_DECLARE_TRACKER(my_tracker, my_dialog, my_metadata)
  ```
</CodeGroup>

### Tracker Best Practices

<Warning>
  The `data()` method is called **250 times per second**. Never perform blocking operations or heavy computation in this method.
</Warning>

<CardGroup cols={2}>
  <Card title="Use Separate Threads">
    Perform heavy computation (image processing, sensor polling) in a separate thread and cache results
  </Card>

  <Card title="Initialize in start_tracker()">
    Open devices, allocate resources, and start threads in `start_tracker()`, not in the constructor
  </Card>

  <Card title="Return Status Properly">
    Return `status_ok()` on success or `error("message")` with a descriptive error message
  </Card>

  <Card title="Clean Up in Destructor">
    Stop threads, release resources, and close devices in the destructor
  </Card>
</CardGroup>

## Developing a Filter Plugin

### Filter Interface

```cpp theme={null}
struct OTR_API_EXPORT IFilter : module_status_mixin
{
    IFilter();
    ~IFilter() override;
    
    // Initialize the filter
    virtual module_status initialize() = 0;
    
    // Perform filtering step
    // You have to handle dt (delta time) yourself
    virtual void filter(const double *input, double *output) = 0;
    
    // Optionally reset the filter when centering
    virtual void center() {}
};
```

### Filter Example

```cpp theme={null}
class my_filter : public IFilter
{
public:
    my_filter();
    
    module_status initialize() override
    {
        // Initialize filter state
        for (int i = 0; i < 6; i++)
            last_output[i] = 0;
        
        return status_ok();
    }
    
    void filter(const double *input, double *output) override
    {
        // Simple exponential smoothing
        const double alpha = 0.3;  // Smoothing factor
        
        for (int i = 0; i < 6; i++)
        {
            output[i] = alpha * input[i] + (1 - alpha) * last_output[i];
            last_output[i] = output[i];
        }
    }
    
    void center() override
    {
        // Reset filter state when user centers
        for (int i = 0; i < 6; i++)
            last_output[i] = 0;
    }
    
private:
    double last_output[6];
};

OPENTRACK_DECLARE_FILTER(my_filter, my_filter_dialog, my_metadata)
```

## Developing a Protocol Plugin

### Protocol Interface

```cpp theme={null}
struct OTR_API_EXPORT IProtocol : module_status_mixin
{
    IProtocol();
    ~IProtocol() override;
    
    // Initialize the protocol
    virtual module_status initialize() = 0;
    
    // Called 250 times a second with XYZ yaw pitch roll pose
    // Try not to perform intense computation here - use a thread
    virtual void pose(const double* pose, const double* raw) = 0;
    
    // Return game name or placeholder text
    virtual QString game_name() = 0;
};
```

### Protocol Example

```cpp theme={null}
class my_protocol : public IProtocol
{
public:
    my_protocol() : sock(this) {}
    
    module_status initialize() override
    {
        if (!sock.bind(QHostAddress::Any, 4242))
            return error(tr("Can't bind to port 4242"));
        
        return status_ok();
    }
    
    void pose(const double* pose, const double* raw) override
    {
        // Send pose data over UDP
        QByteArray data;
        QDataStream stream(&data, QIODevice::WriteOnly);
        
        for (int i = 0; i < 6; i++)
            stream << pose[i];
        
        sock.writeDatagram(data, QHostAddress::LocalHost, 4242);
    }
    
    QString game_name() override
    {
        return tr("My Game Protocol");
    }
    
private:
    QUdpSocket sock;
};

OPENTRACK_DECLARE_PROTOCOL(my_protocol, my_protocol_dialog, my_metadata)
```

## Plugin Metadata

### Metadata Class

Every plugin must provide metadata:

```cpp theme={null}
class OTR_API_EXPORT Metadata_
{
public:
    Metadata_();
    virtual ~Metadata_();
    
    // Plugin name displayed in the interface
    virtual QString name() = 0;
    
    // Plugin icon (can return empty QIcon())
    virtual QIcon icon() = 0;
};

// Use the Metadata base class for Qt support
class OTR_API_EXPORT Metadata : public TR, public Metadata_
{
    Q_OBJECT
public:
    Metadata();
    ~Metadata() override;
};
```

## Plugin Dialogs

Plugins can provide configuration dialogs:

### Base Dialog

```cpp theme={null}
class OTR_API_EXPORT BaseDialog : public QDialog
{
    Q_OBJECT
protected:
    BaseDialog();
public:
    void closeEvent(QCloseEvent *) override;
    virtual bool embeddable() noexcept;
    virtual void set_buttons_visible(bool x);
    virtual void save();     // Save settings
    virtual void reload();   // Reload settings
signals:
    void closing();
};
```

### Dialog Example

```cpp theme={null}
class my_dialog : public ITrackerDialog
{
    Q_OBJECT
    
    Ui::my_ui ui;  // From .ui file
    
public:
    my_dialog()
    {
        ui.setupUi(this);
        connect(ui.buttonBox, &QDialogButtonBox::accepted,
                this, &my_dialog::doOK);
        connect(ui.buttonBox, &QDialogButtonBox::rejected,
                this, &my_dialog::doCancel);
    }
    
    void register_tracker(ITracker *tracker) override
    {
        // Receive pointer to tracker instance
        m_tracker = tracker;
    }
    
    void unregister_tracker() override
    {
        // Tracker is about to be deleted
        m_tracker = nullptr;
    }
    
private slots:
    void doOK() { save(); close(); }
    void doCancel() { close(); }
    
private:
    ITracker* m_tracker = nullptr;
};
```

## Plugin Declaration Macros

Use these macros to export your plugin:

```cpp theme={null}
// For trackers
OPENTRACK_DECLARE_TRACKER(tracker_class, dialog_class, metadata_class)

// For filters  
OPENTRACK_DECLARE_FILTER(filter_class, dialog_class, metadata_class)

// For protocols
OPENTRACK_DECLARE_PROTOCOL(protocol_class, dialog_class, metadata_class)
```

These macros expand to export the required functions:

```cpp theme={null}
extern "C"
{
    OTR_PLUGIN_EXPORT ConstructorClass* GetConstructor(void);
    OTR_PLUGIN_EXPORT Metadata_* GetMetadata(void);
    OTR_PLUGIN_EXPORT DialogClass* GetDialog(void);
}
```

## Module Status Handling

### Returning Status

```cpp theme={null}
// Return success
return status_ok();

// Return error with message
return error(tr("Failed to open device"));

// Check if status is OK
module_status s = initialize();
if (!s.is_ok())
{
    qDebug() << "Error:" << s.error;
}
```

## Building and Testing

<Steps>
  <Step title="Build Your Plugin">
    ```bash theme={null}
    cd build
    cmake ..
    make opentrack-tracker-myplugin
    ```
  </Step>

  <Step title="Install Plugin">
    ```bash theme={null}
    make install
    ```

    Plugins are installed to `${CMAKE_INSTALL_PREFIX}/lib/opentrack/` or similar.
  </Step>

  <Step title="Test in OpenTrack">
    Run OpenTrack and select your plugin from the appropriate dropdown (Input, Filter, or Output).
  </Step>
</Steps>

## Additional Resources

<CardGroup cols={2}>
  <Card title="Plugin API Header" icon="file-code" href="https://github.com/opentrack/opentrack/blob/master/api/plugin-api.hpp">
    Complete API reference with documentation
  </Card>

  <Card title="Test Tracker Example" icon="flask" href="https://github.com/opentrack/opentrack/tree/master/tracker-test">
    Simple working example plugin
  </Card>

  <Card title="Core Hacking Guide" icon="book" href="https://github.com/opentrack/opentrack/wiki/Hacking-opentrack">
    Guide for working with OpenTrack core code
  </Card>

  <Card title="Building from Source" icon="hammer" href="/advanced/building-from-source">
    Set up your build environment
  </Card>
</CardGroup>

## Tips and Best Practices

<AccordionGroup>
  <Accordion title="Thread Safety">
    The UI thread and tracking thread are separate. Use proper synchronization (mutexes, atomics) when sharing data between threads.
  </Accordion>

  <Accordion title="Performance">
    * Avoid memory allocations in hot paths (`data()`, `filter()`, `pose()`)
    * Use double-buffering for thread communication
    * Profile your code to find bottlenecks
  </Accordion>

  <Accordion title="Error Handling">
    * Always return descriptive error messages
    * Use `qDebug()` for logging during development
    * Handle device disconnection gracefully
  </Accordion>

  <Accordion title="Qt Integration">
    * Use Qt's signal/slot mechanism for UI updates
    * Store settings using `opentrack-options` API
    * Support translations with `tr()` strings
  </Accordion>
</AccordionGroup>
