Vai al contenuto

Scrivere un plugin nativo

Un plugin nativo minimo è una classe, una macro di fabbrica, un file JSON e un breve CMakeLists.txt. Puoi scriverlo interamente fuori dall'albero dei sorgenti, appoggiandoti all'SDK per plugin: non serve una copia dei sorgenti di MTE.

Nativo o Python?

Questa pagina tratta i plugin nativi in C++ (compilati in un .dll/.so/.dylib e caricati dal caricatore Qt). Se preferisci distribuire una cartella di file .py che l'editor avvia come sottoprocesso, vedi Plugin Python — stessa superficie di PluginApi, backend diverso, nessun compilatore richiesto.

Bozza

Questa guida è basata su PLUGIN_ARCHITECTURE.md; verifica il nome esatto del pacchetto SDK e gli argomenti di mte_add_plugin() rispetto all'SDK distribuito prima di farci affidamento.

1. Implementa IPlugin

#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. Esporta la fabbrica (backend Qt nativo)

#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. Descrivilo in plugin.json

{
  "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 }
  ]
}

L'apiVersion qui e quella restituita da info() devono essere entrambe uguali a MTE_PLUGIN_API_VERSION, altrimenti l'host rifiuta di caricare il plugin.

La accompagnano due chiavi facoltative:

  • apiMinorVersion (predefinito 0) — la versione minor minima dell'API dell'host di cui il tuo plugin ha bisogno. Un host la cui MTE_PLUGIN_API_MINOR_VERSION è inferiore elenca il plugin con «Richiede una versione più recente di OctoEdit» e non lo carica. L'host controlla questo valore del manifesto prima di caricare qualsiasi cosa, e poi di nuovo PluginInfo::apiMinorVersion restituito da info(): quest'ultimo vale per impostazione predefinita la minor degli header con cui hai compilato, quindi non toccarlo a meno che tu non sappia di non usare nulla di più recente. Vedi Versionamento.
  • requires (predefinito []) — gli id dei plugin senza i quali il tuo plugin non può funzionare, per esempio ["org.mte.lsp"]. Solo id: non c'è un intervallo di versioni né una forma facoltativa — un plugin con cui il tuo funziona semplicemente meglio non va qui; dialoga con lui in fase di esecuzione tramite eventi e registri di fornitori. L'editor inizializza le tue dipendenze prima di te, qualunque cosa dica order, e le arresta dopo di te. Se una di esse manca, è disattivata, richiede una licenza non attiva o non è stata caricata, il tuo plugin non viene caricato affatto, e Preferenze ▸ Estensioni dice di quale plugin ha bisogno e perché («Richiede l'estensione LSP Provider, che è disattivata»). Un ciclo blocca ogni plugin che ne fa parte. La stessa chiave ha lo stesso significato nel plugin.json di un plugin Python.

Attribuire le librerie incluse — thirdParty

Se il tuo plugin include o collega un componente di terze parti, dichiaralo in un array facoltativo thirdParty, così che l'editor possa onorarne gli obblighi di attribuzione:

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

Ogni voce richiede un name (le voci che ne sono prive vengono scartate); license, url e file sono facoltativi. I testi delle licenze appartengono al tuo plugin: distribuiscili accanto al plugin sotto Plugins/Licenses/<your-plugin-id>/, dove file è il solo nome del file. La finestra Informazioni elenca i componenti thirdParty di ogni plugin installato e indirizza gli utenti a quella cartella; il nucleo non porta mai con sé i testi di terze parti di un plugin. (Il plugin incluso HtmlPreviewPlugin, che incorpora QtWebEngine + Chromium, è l'esempio di riferimento.)

4. Compilalo

Usa l'ausilio CMake dell'SDK:

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

Requisiti di compilazione

Un plugin nativo viene caricato nel processo dell'editor, quindi dev'essere compilato con una toolchain binariamente compatibile con la build dell'editor a cui è destinato. Questi sono i requisiti per la versione attuale (compilata con Qt 6.11.2):

Windows macOS
Compilatore Visual Studio 2022, toolset MSVC 14.44 o un 14.x più vecchio (VS 2015–2022 condividono un'unica ABI C++); va bene anche clang-cl. Le build MinGW / GCC non possono essere caricate. Apple clang da Xcode (Xcode 27, Apple clang 21.0.0); solo arm64
Configurazione di compilazione Release, runtime dinamico (/MD, il valore predefinito di CMake). Un plugin Debug viene rifiutato dal caricatore; un plugin con runtime statico (/MT) va in crash quando la memoria attraversa il confine del plugin Release; deployment target 14.4 (lo stesso dell'editor)
Qt Qt 6.11, kit msvc2022_64. Un plugin compilato con una minor 6.x più vecchia si carica; uno compilato con una minor più recente potrebbe non caricarsi Qt 6.11, stessa architettura
Standard C++ C++20 (le intestazioni dell'API usano std::span e i concept) C++20
CMake 3.16+ 3.16+
API per plugin la MTE_PLUGIN_API_VERSION dell'SDK distribuito con la versione dell'editor a cui punti idem

Due conseguenze che vale la pena conoscere su Windows:

  • Il Visual C++ Redistributable installato sulla macchina dell'utente deve essere almeno recente quanto il più recente toolset MSVC usato dall'editor o da un qualsiasi plugin caricato. Compilare il tuo plugin con un toolset più recente di quello dell'editor alza quel requisito per i tuoi utenti; compilarlo con il toolset dell'editor o con uno più vecchio no.
  • La versione del Windows SDK con cui compili non influisce sulla compatibilità con l'editor; decide soltanto quali API di Windows il tuo plugin può chiamare, e l'editor supporta Windows 10 1809 e successivi.

E su macOS:

  • L'editor viene distribuito solo per arm64; un plugin solo x86_64 non può essere caricato (un binario universale va bene). Mantieni il deployment target a 14.4: un plugin compilato per un macOS minimo più recente viene rifiutato dal caricatore di sistema sui Mac più vecchi e, poiché il runtime è la libc++ del sistema operativo stesso, non si pone alcuna questione di redistributable.
  • Fai riferimento ai framework Qt tramite @rpath, così che il plugin prenda le copie incluse dentro OctoEdit.app. I kit Qt i cui framework portano nomi di installazione assoluti (per esempio quelli di Homebrew) richiedono install_name_tool sul plugin compilato prima della distribuzione; altrimenti il plugin cercherà Qt nel percorso della macchina di compilazione.

Niente di tutto ciò vale per i plugin Python: girano in un processo separato e non condividono alcuna interfaccia binaria con l'editor.

5. Installa ed esegui

Puoi comprimere la libreria compilata + plugin.json come <name>.mteplugin e installarlo tramite Preferenze ▸ Estensioni ▸ Installa estensione... (il pacchetto viene validato — manifest, sicurezza dell'archivio e metadati incorporati nel binario — prima che venga toccato alcunché), oppure depositare i file a mano in una cartella di plugin individuata (vedi Panoramica dell'architettura). Riavvia l'editor e, se serve, abilitalo in Preferenze ▸ Estensioni.

Prossimi passi