Zum Inhalt

Ein natives Plugin schreiben

Ein minimales natives Plugin besteht aus einer Klasse, einem Factory-Makro, einer JSON-Datei und einer kurzen CMakeLists.txt. Sie können es vollständig außerhalb des Quellbaums gegen das Plugin-SDK entwickeln — ein Checkout der MTE-Quellen ist nicht erforderlich.

Nativ oder Python?

Diese Seite behandelt native C++-Plugins (kompiliert zu einer .dll/.so/.dylib und vom Qt-Loader geladen). Wenn Sie lieber einen Ordner mit .py-Dateien ausliefern, den der Editor als Subprozess startet, lesen Sie Python-Plugins — dieselbe PluginApi-Oberfläche, anderes Backend, kein Compiler nötig.

Entwurf

Diese Anleitung basiert auf PLUGIN_ARCHITECTURE.md; prüfen Sie den genauen SDK-Paketnamen und die Argumente von mte_add_plugin() gegen das ausgelieferte SDK, bevor Sie sich darauf verlassen.

1. IPlugin implementieren

#include <PluginApi/IPlugin.h>
#include <PluginApi/IPluginContext.h>

class HelloWorldPlugin : public MTE::plugin::IPlugin {
public:
    MTE::plugin::PluginInfo info() const override {
        return { "org.mte.helloworld", "Hello World", "1.0.0",
                 "Example Vendor", "Says hi.", MTE_PLUGIN_API_VERSION };
    }

    bool initialize(MTE::plugin::IPluginContext& ctx) override {
        m_cmd = ctx.commands().registerCommand(
            { "helloworld.sayHi", "Say Hi", "Ctrl+Alt+H", "Hello" },
            [&ctx] { ctx.log().log(MTE::plugin::LogLevel::Info, "Hi!"); });
        m_menu = ctx.menus().addItem("Plugins/Say Hi", "helloworld.sayHi");
        return true;
    }

    void shutdown() noexcept override {}   // tokens tear down automatically

private:
    MTE::plugin::CommandToken m_cmd;
    MTE::plugin::MenuToken    m_menu;
};

2. Die Factory exportieren (natives Qt-Backend)

#include <PluginApi/Qt/PluginQtGlue.h>

class HelloWorldFactory : public QObject, public MTE::plugin::IPluginFactory {
    Q_OBJECT
    Q_PLUGIN_METADATA(IID "org.mte.IPluginFactory/1" FILE "plugin.json")
    Q_INTERFACES(MTE::plugin::IPluginFactory)
public:
    std::unique_ptr<MTE::plugin::IPlugin> create() override {
        return std::make_unique<HelloWorldPlugin>();
    }
};

3. In plugin.json beschreiben

{
  "id":         "org.mte.helloworld",
  "name":       "Hello World",
  "version":    "1.0.0",
  "apiVersion": 1,
  "entry":      "native",
  "requires":   [],
  "order":      600,
  "menus": [
    { "title": "Plugins", "barPriority": 570, "itemPriority": 100 }
  ]
}

Die apiVersion hier und die von info() zurückgegebene müssen beide gleich MTE_PLUGIN_API_VERSION sein, andernfalls verweigert der Host das Laden des Plugins.

Dazu gehören zwei optionale Schlüssel:

  • apiMinorVersion (Standard 0) — die niedrigste Minor-Version der Host-API, die Ihr Plugin braucht. Ein Host, dessen MTE_PLUGIN_API_MINOR_VERSION niedriger ist, führt das Plugin mit „Erfordert eine neuere Version von OctoEdit“ und lädt es nicht. Der Host prüft diesen Manifestwert, bevor er irgendetwas lädt, und danach noch einmal PluginInfo::apiMinorVersion aus info() — dieser Wert ist standardmäßig die Minor-Version der Header, gegen die Sie kompiliert haben; lassen Sie ihn also in Ruhe, es sei denn, Sie wissen, dass Sie nichts Neueres verwenden. Siehe Versionierung.
  • requires (Standard []) — die IDs der Plugins, ohne die Ihr Plugin nicht funktioniert, zum Beispiel ["org.mte.lsp"]. Nur IDs: Es gibt keinen Versionsbereich und keine optionale Form — ein Plugin, mit dem Ihres lediglich besser funktioniert, gehört nicht hierher; sprechen Sie zur Laufzeit über Events und Provider-Registries mit ihm. Der Editor initialisiert Ihre Abhängigkeiten vor Ihrem Plugin, egal was order sagt, und fährt sie nach ihm herunter. Fehlt eine davon, ist sie deaktiviert, braucht sie eine Lizenz, die nicht aktiv ist, oder wurde sie nicht geladen, dann wird Ihr Plugin überhaupt nicht geladen, und Einstellungen ▸ Erweiterungen zeigt, welches Plugin es braucht und warum („Benötigt das Plug-in LSP Provider, das deaktiviert ist“). Ein Zyklus blockiert jedes Plugin darin. Derselbe Schlüssel bedeutet dasselbe in der plugin.json eines Python-Plugins.

Mitgelieferte Bibliotheken attributieren — thirdParty

Wenn Ihr Plugin eine Drittanbieter-Komponente bündelt oder dagegen linkt, deklarieren Sie sie in einem optionalen thirdParty-Array, damit der Editor seine Attributionspflichten erfüllen kann:

"thirdParty": [
  { "name": "Qt WebEngine", "license": "LGPL-3.0",
    "url": "https://doc.qt.io/qt-6/qtwebengine-index.html",
    "file": "QtWebEngine-LGPLv3.txt" }
]

Jeder Eintrag benötigt einen name (Einträge ohne Namen werden verworfen); license, url und file sind optional. Ihr Plugin ist Eigentümer der Lizenztexte — liefern Sie sie neben dem Plugin unter Plugins/Licenses/<your-plugin-id>/ aus, wobei file der Basisname ist. Der Über-Dialog listet die thirdParty-Komponenten jedes installierten Plugins auf und verweist die Benutzer auf diesen Ordner; der Kern trägt die Drittanbieter-Texte eines Plugins niemals selbst. (Das mitgelieferte HtmlPreviewPlugin, das QtWebEngine + Chromium einbettet, ist das Referenzbeispiel.)

4. Bauen

Verwenden Sie den CMake-Helfer des SDK:

find_package(MTEPluginSDK REQUIRED)
mte_add_plugin(NAME HelloWorldPlugin
    SOURCES Src/HelloWorldPlugin.cpp
    # TRANSLATIONS Translations/HelloWorld_en.ts ...
)

Build-Voraussetzungen

Ein natives Plugin wird in den Prozess des Editors geladen und muss deshalb mit einer Toolchain gebaut werden, die binärkompatibel zu dem Editor-Build ist, für den es bestimmt ist. Die Anforderungen für die aktuelle Version (gebaut mit Qt 6.11.2):

Windows macOS
Compiler Visual Studio 2022, MSVC-Toolset 14.44 oder ein älteres 14.x (VS 2015–2022 teilen sich eine C++-ABI); clang-cl funktioniert ebenfalls. MinGW-/GCC-Builds lassen sich nicht laden. Apple clang aus Xcode (Xcode 27, Apple clang 21.0.0); nur arm64
Build-Konfiguration Release, dynamische Laufzeitbibliothek (/MD, CMake-Standard). Ein Debug-Plugin weist der Loader ab; ein Plugin mit statischer Laufzeitbibliothek (/MT) stürzt ab, sobald Speicher die Plugin-Grenze überschreitet Release; Deployment-Target 14.4 (wie der Editor)
Qt Qt 6.11, Kit msvc2022_64. Ein gegen ein älteres 6.x gebautes Plugin lädt; eines gegen eine neuere Minor-Version möglicherweise nicht Qt 6.11, gleiche Architektur
C++-Standard C++20 (die API-Header verwenden std::span und Concepts) C++20
CMake 3.16+ 3.16+
Plugin-API die MTE_PLUGIN_API_VERSION des SDK, das mit der Ziel-Editorversion ausgeliefert wurde ebenso

Zwei Folgen unter Windows, die man kennen sollte:

  • Das Visual C++ Redistributable auf dem Rechner des Anwenders muss mindestens so neu sein wie das neueste MSVC-Toolset, das der Editor oder ein geladenes Plugin verwendet. Ein Plugin mit einem neueren Toolset als dem des Editors zu bauen erhöht diese Anforderung für Ihre Anwender; das Toolset des Editors oder ein älteres tut das nicht.
  • Die Version des Windows SDK, gegen das Sie kompilieren, beeinflusst die Kompatibilität mit dem Editor nicht; sie bestimmt nur, welche Windows-APIs Ihr Plugin aufrufen kann — der Editor unterstützt Windows 10 1809 und neuer.

Und unter macOS:

  • Der Editor wird nur als arm64 ausgeliefert; ein reines x86_64-Plugin lässt sich nicht laden (ein Universal-Binary schon). Belassen Sie das Deployment-Target bei 14.4: ein Plugin, das für ein neueres Mindest-macOS gebaut wurde, weist der System-Loader auf älteren Macs ab, und die libc++ des Systems ist die Laufzeitbibliothek — eine Redistributable-Frage stellt sich nicht.
  • Referenzieren Sie die Qt-Frameworks über @rpath, damit das Plugin die in OctoEdit.app gebündelten Kopien verwendet. Qt-Kits, deren Frameworks absolute Install-Namen tragen (etwa Homebrews), brauchen vor der Auslieferung install_name_tool auf dem gebauten Plugin; sonst sucht das Plugin Qt unter dem Pfad des Build-Rechners.

Nichts davon gilt für Python-Plugins: Sie laufen in einem eigenen Prozess und teilen keine Binärschnittstelle mit dem Editor.

5. Installieren & ausführen

Packen Sie entweder die gebaute Bibliothek + plugin.json als <name>.mteplugin in ein Zip und installieren Sie es über Einstellungen ▸ Erweiterungen ▸ Plugin installieren... (das Paket wird validiert — Manifest, Archivsicherheit und die eingebetteten Metadaten der Binärdatei —, bevor irgendetwas angefasst wird), oder legen Sie die Dateien von Hand in ein erkanntes Plugin-Verzeichnis (siehe Architekturüberblick). Starten Sie den Editor neu; aktivieren Sie das Plugin bei Bedarf unter Einstellungen ▸ Erweiterungen.

Weiter