É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(0par défaut) — la version mineure minimale de l'API hôte dont votre plugin a besoin. Un hôte dontMTE_PLUGIN_API_MINOR_VERSIONest 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 à nouveauPluginInfo::apiMinorVersionrenvoyé parinfo()— 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'indiqueorder, 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 leplugin.jsond'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_64seul 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
@rpathafin que le plugin utilise les copies embarquées dansOctoEdit.app. Les kits Qt dont les frameworks portent des noms d'installation absolus (ceux de Homebrew, par exemple) nécessitentinstall_name_toolsur 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¶
- Vue d'ensemble de l'API de plugin — cycle de vie, jetons et le tableau complet des services
- Commandes et menus
- Panneaux, onglets et barre d'état
- Pages de réglages
- Événements
- Traductions