Aller au contenu

Écrire un plugin natif

Un plugin natif minimal, c'est une classe, une macro de fabrique, un fichier JSON et un court CMakeLists.txt. Vous pouvez l'écrire entièrement hors de l'arborescence, contre le Plugin SDK — aucune copie des sources de MTE n'est requise.

Natif ou Python ?

Cette page couvre les plugins C++ natifs (compilés en .dll/.so/.dylib et chargés par le chargeur Qt). Si vous préférez livrer un dossier de fichiers .py que l'éditeur lance comme sous-processus, consultez les plugins Python — même surface PluginApi, backend différent, aucun compilateur requis.

Brouillon

Ce guide pas à pas s'appuie sur PLUGIN_ARCHITECTURE.md ; vérifiez le nom exact du paquet SDK et les arguments de mte_add_plugin() par rapport au SDK livré avant de vous y fier.

1. Implémenter 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. Exporter la fabrique (backend natif 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. Le décrire dans 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 déclaré ici et celui renvoyé par info() doivent tous deux être égaux à MTE_PLUGIN_API_VERSION, sinon l'hôte refuse de charger le plugin.

Deux clés facultatives l'accompagnent :

  • apiMinorVersion (0 par défaut) — la version mineure minimale de l'API hôte dont votre plugin a besoin. Un hôte dont MTE_PLUGIN_API_MINOR_VERSION est inférieure affiche le plugin avec « Nécessite une version plus récente d'OctoEdit » et ne le charge pas. L'hôte vérifie cette valeur du manifeste avant de charger quoi que ce soit, puis à nouveau PluginInfo::apiMinorVersion renvoyé par info() — celle-ci vaut par défaut la mineure des en-têtes avec lesquels vous avez compilé ; n'y touchez donc pas, sauf si vous savez que vous n'utilisez rien de plus récent. Voir Versionnement.
  • requires ([] par défaut) — les identifiants des plugins sans lesquels votre plugin ne peut pas fonctionner, par exemple ["org.mte.lsp"]. Des identifiants seulement : pas de plage de versions ni de forme facultative — un plugin avec lequel le vôtre fonctionne simplement mieux n'a pas sa place ici ; dialoguez avec lui à l'exécution par les événements et les registres de fournisseurs. L'éditeur initialise vos dépendances avant vous, quoi qu'indique order, et les arrête après vous. Si l'une d'elles est absente, désactivée, exige une licence qui n'est pas active ou ne s'est pas chargée, votre plugin n'est pas chargé du tout, et Préférences ▸ Extensions indique de quel plugin il a besoin et pourquoi (« Nécessite le plugin LSP Provider, qui est désactivé »). Un cycle bloque tous les plugins qui en font partie. La même clé a le même sens dans le plugin.json d'un plugin Python.

Attribuer les bibliothèques embarquées — thirdParty

Si votre plugin embarque ou lie un composant tiers, déclarez-le dans un tableau optionnel thirdParty afin que l'éditeur puisse honorer ses obligations d'attribution :

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

Chaque entrée requiert un name (les entrées sans nom sont ignorées) ; license, url et file sont optionnels. Votre plugin est propriétaire des textes de licence — livrez-les à côté du plugin sous Plugins/Licenses/<your-plugin-id>/, où file est le nom de base du fichier. La boîte de dialogue À propos liste les composants thirdParty de chaque plugin installé et oriente les utilisateurs vers ce dossier ; le cœur de l'éditeur ne porte jamais les textes tiers d'un plugin à sa place. (Le plugin fourni HtmlPreviewPlugin, qui embarque QtWebEngine + Chromium, est l'exemple de référence.)

4. Le compiler

Utilisez l'assistant CMake du SDK :

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

Prérequis de compilation

Un plugin natif est chargé dans le processus de l'éditeur ; il doit donc être compilé avec une chaîne d'outils compatible au niveau binaire avec la version de l'éditeur visée. Voici les prérequis pour la version actuelle (compilée avec Qt 6.11.2) :

Windows macOS
Compilateur Visual Studio 2022, toolset MSVC 14.44 ou un 14.x plus ancien (VS 2015–2022 partagent une même ABI C++) ; clang-cl fonctionne aussi. Les builds MinGW / GCC ne peuvent pas être chargés. Apple clang de Xcode (Xcode 27, Apple clang 21.0.0) ; arm64 uniquement
Configuration Release, runtime dynamique (/MD, valeur par défaut de CMake). Un plugin Debug est refusé par le chargeur ; un plugin avec runtime statique (/MT) plante dès que de la mémoire traverse la frontière du plugin Release ; cible de déploiement 14.4 (comme l'éditeur)
Qt Qt 6.11, kit msvc2022_64. Un plugin compilé avec une version 6.x plus ancienne se charge ; un plugin compilé avec une version mineure plus récente peut ne pas se charger Qt 6.11, même architecture
Standard C++ C++20 (les en-têtes de l'API utilisent std::span et les concepts) C++20
CMake 3.16+ 3.16+
API des plugins le MTE_PLUGIN_API_VERSION du SDK livré avec la version de l'éditeur visée idem

Deux conséquences à connaître sous Windows :

  • Le Visual C++ Redistributable installé sur la machine de l'utilisateur doit être au moins aussi récent que le toolset MSVC le plus récent utilisé par l'éditeur ou par n'importe quel plugin chargé. Compiler votre plugin avec un toolset plus récent que celui de l'éditeur relève cette exigence pour vos utilisateurs ; utiliser le toolset de l'éditeur ou un plus ancien ne la change pas.
  • La version du Windows SDK utilisée pour compiler n'a aucun effet sur la compatibilité avec l'éditeur ; elle détermine seulement les API Windows que votre plugin peut appeler, l'éditeur prenant en charge Windows 10 1809 et ultérieur.

Et sous macOS :

  • L'éditeur est livré uniquement en arm64 ; un plugin x86_64 seul ne peut pas se charger (un binaire universel convient). Conservez la cible de déploiement 14.4 : un plugin compilé pour un macOS minimum plus récent est refusé par le chargeur système sur les Mac plus anciens, et la libc++ du système sert de runtime, donc la question du redistributable ne se pose pas.
  • Référencez les frameworks Qt via @rpath afin que le plugin utilise les copies embarquées dans OctoEdit.app. Les kits Qt dont les frameworks portent des noms d'installation absolus (ceux de Homebrew, par exemple) nécessitent install_name_tool sur le plugin compilé avant diffusion ; sinon le plugin cherche Qt au chemin de la machine de compilation.

Rien de tout cela ne s'applique aux plugins Python : ils s'exécutent dans un processus séparé et ne partagent aucune interface binaire avec l'éditeur.

5. Installer et lancer

Soit vous compressez la bibliothèque compilée + plugin.json en <name>.mteplugin et l'installez via Préférences ▸ Extensions ▸ Installer une extension... (le paquet est validé — manifeste, sûreté de l'archive et métadonnées embarquées dans le binaire — avant que quoi que ce soit ne soit touché), soit vous déposez les fichiers à la main dans un répertoire de plugins découvert (voir la vue d'ensemble de l'architecture). Redémarrez l'éditeur ; activez le plugin dans Préférences ▸ Extensions si nécessaire.

Étapes suivantes