Saltar a contenido

Escribir un plugin nativo

Un plugin nativo mínimo es una clase, una macro de fábrica, un archivo JSON y un CMakeLists.txt corto. Puedes crearlo por completo fuera del árbol del proyecto, contra el SDK de plugins, sin necesidad de descargar el código fuente de MTE.

¿Nativo o Python?

Esta página cubre los plugins nativos de C++ (compilados a un .dll/.so/.dylib y cargados por el cargador de Qt). Si prefieres distribuir una carpeta de archivos .py que el editor lanza como subproceso, consulta Plugins de Python: misma superficie de PluginApi, backend distinto, sin necesidad de compilador.

Borrador

Este recorrido está basado en PLUGIN_ARCHITECTURE.md; verifica el nombre exacto del paquete del SDK y los argumentos de mte_add_plugin() contra el SDK distribuido antes de confiar en ellos.

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. Exporta la fábrica (backend nativo de Qt)

#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. Descríbelo en 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 }
  ]
}

El apiVersion de aquí y el que devuelve info() deben ser ambos iguales a MTE_PLUGIN_API_VERSION, o el anfitrión se negará a cargar el plugin.

Lo acompañan dos claves opcionales:

  • apiMinorVersion (0 de forma predeterminada): la versión menor mínima de la API del anfitrión que necesita tu plugin. Un anfitrión cuya MTE_PLUGIN_API_MINOR_VERSION sea inferior muestra el plugin con «Necesita una versión más reciente de OctoEdit» y no lo carga. El anfitrión comprueba este valor del manifiesto antes de cargar nada, y después vuelve a comprobar PluginInfo::apiMinorVersion de info(); ese valor es por defecto la versión menor de las cabeceras con las que compilaste, así que no lo toques a menos que sepas que no usas nada más reciente. Consulta Versionado.
  • requires ([] de forma predeterminada): los identificadores de los plugins sin los que tu plugin no puede funcionar, por ejemplo ["org.mte.lsp"]. Solo identificadores: no hay rango de versiones ni forma opcional; un plugin con el que el tuyo simplemente funciona mejor no va aquí: comunícate con él en tiempo de ejecución mediante eventos y registros de proveedores. El editor inicializa tus dependencias antes que tu plugin, diga lo que diga order, y las apaga después. Si alguna falta, está desactivada, necesita una licencia que no está activa o no se cargó, tu plugin no se carga en absoluto, y Preferencias ▸ Complementos indica qué plugin necesita y por qué («Necesita el complemento LSP Provider, que está desactivado»). Un ciclo bloquea todos los plugins que forman parte de él. La misma clave significa lo mismo en el plugin.json de un plugin Python.

Atribuir las bibliotecas incluidas — thirdParty

Si tu plugin incluye o enlaza un componente de terceros, decláralo en una matriz opcional thirdParty para que el editor pueda cumplir sus obligaciones de atribución:

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

Cada entrada necesita un name (las entradas sin él se descartan); license, url y file son opcionales. Las licencias son responsabilidad de tu plugin: distribúyelas junto a él, bajo Plugins/Licenses/<your-plugin-id>/, donde file es el nombre base. El diálogo Acerca de lista los componentes thirdParty de todos los plugins instalados y remite a los usuarios a esa carpeta; el núcleo nunca transporta por un plugin los textos de terceros de este. (El HtmlPreviewPlugin incluido, que integra QtWebEngine + Chromium, es el ejemplo de referencia.)

4. Compílalo

Usa el ayudante de CMake del SDK:

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

Requisitos de compilación

Un plugin nativo se carga dentro del proceso del editor, así que tiene que compilarse con una cadena de herramientas binariamente compatible con la compilación del editor a la que va dirigido. Estos son los requisitos de la versión actual (compilada con Qt 6.11.2):

Windows macOS
Compilador Visual Studio 2022, conjunto de herramientas MSVC 14.44 o uno 14.x anterior (VS 2015–2022 comparten una ABI de C++); clang-cl también funciona. Las compilaciones con MinGW / GCC no se pueden cargar. Apple clang de Xcode (Xcode 27, Apple clang 21.0.0); solo arm64
Configuración de compilación Release, tiempo de ejecución dinámico (/MD, lo predeterminado de CMake). El cargador rechaza un plugin en Debug; un plugin con tiempo de ejecución estático (/MT) falla cuando la memoria cruza el límite del plugin Release; destino de despliegue 14.4 (el mismo que el editor)
Qt Qt 6.11, kit msvc2022_64. Un plugin compilado contra una versión menor 6.x anterior se carga; uno compilado contra una menor más nueva puede que no Qt 6.11, la misma arquitectura
Estándar de C++ C++20 (las cabeceras de la API usan std::span y conceptos) C++20
CMake 3.16+ 3.16+
Plugin API el MTE_PLUGIN_API_VERSION del SDK que se distribuyó con la versión del editor a la que apuntas igual

Dos consecuencias que conviene conocer en Windows:

  • El Visual C++ Redistributable instalado en la máquina del usuario tiene que ser al menos tan nuevo como el conjunto de herramientas MSVC más nuevo que use el editor o cualquier plugin cargado. Compilar tu plugin con un conjunto de herramientas más nuevo que el del editor eleva ese requisito para tus usuarios; compilar con el del editor o con uno anterior, no.
  • La versión del SDK de Windows contra la que compilas no afecta a la compatibilidad con el editor; solo decide a qué API de Windows puede llamar tu plugin, y el editor admite Windows 10 1809 y posteriores.

Y en macOS:

  • El editor se distribuye solo para arm64; un plugin solo para x86_64 no se puede cargar (un binario universal sí vale). Mantén el destino de despliegue en 14.4: un plugin compilado para un macOS mínimo más nuevo lo rechaza el cargador del sistema en Mac más antiguos, y el tiempo de ejecución es la propia libc++ del sistema operativo, así que no se plantea la cuestión del redistribuible.
  • Referencia los frameworks de Qt a través de @rpath para que el plugin tome las copias incluidas dentro de OctoEdit.app. Los kits de Qt cuyos frameworks llevan nombres de instalación absolutos (los de Homebrew, por ejemplo) necesitan install_name_tool sobre el plugin compilado antes de distribuirlo; si no, el plugin buscará Qt en la ruta de la máquina de compilación.

Nada de esto se aplica a los plugins de Python: se ejecutan en un proceso aparte y no comparten ninguna interfaz binaria con el editor.

5. Instálalo y ejecútalo

Puedes comprimir la biblioteca compilada y el plugin.json como <name>.mteplugin e instalarlo mediante Preferencias ▸ Complementos ▸ Instalar un complemento... (el paquete se valida —manifiesto, seguridad del archivo comprimido y metadatos incrustados en el binario— antes de tocar nada), o dejar los archivos a mano en un directorio de plugins detectado (consulta la visión general de la arquitectura). Reinicia el editor; actívalo en Preferencias ▸ Complementos si hace falta.

Siguiente