Escrevendo um plugin nativo¶
Um plugin nativo mínimo é uma classe, uma macro de fábrica, um arquivo JSON e um
CMakeLists.txt curto. Você pode criá-lo inteiramente fora da árvore do projeto,
contra o SDK de plugins — sem precisar baixar o código-fonte do MTE.
Nativo ou Python?
Esta página cobre plugins nativos em C++ (compilados em um
.dll/.so/.dylib e carregados pelo carregador do Qt). Se você preferir
distribuir uma pasta de arquivos .py que o editor executa como subprocesso,
veja Plugins Python — mesma superfície de
PluginApi, backend diferente, sem necessidade de compilador.
Rascunho
Este passo a passo está baseado no PLUGIN_ARCHITECTURE.md; verifique o nome
exato do pacote do SDK e os argumentos de mte_add_plugin() contra o SDK
distribuído antes de contar com eles.
1. Implemente 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. Exporte a fábrica (backend nativo do 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. Descreva-o no 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 }
]
}
O apiVersion daqui e o devolvido por info() precisam ser ambos iguais a
MTE_PLUGIN_API_VERSION, ou o host se recusa a carregar o plugin.
Duas chaves opcionais o acompanham:
apiMinorVersion(padrão0) — a menor versão minor da API do host de que o seu plugin precisa. Um host cujaMTE_PLUGIN_API_MINOR_VERSIONseja menor lista o plugin com "Precisa de uma versão mais recente do OctoEdit" e não o carrega. O host verifica esse valor do manifesto antes de carregar qualquer coisa e, depois, verifica de novoPluginInfo::apiMinorVersiondeinfo()— esse valor tem como padrão a minor dos cabeçalhos com que você compilou, então não mexa nele a menos que saiba que não usa nada mais novo. Veja Versionamento.requires(padrão[]) — os ids dos plugins sem os quais o seu plugin não funciona, por exemplo["org.mte.lsp"]. Só ids: não há faixa de versões nem forma opcional — um plugin com o qual o seu apenas funciona melhor não entra aqui; converse com ele em tempo de execução por meio de eventos e registros de provedores. O editor inicializa suas dependências antes do seu plugin, seja qual for oorder, e as encerra depois dele. Quando alguma está ausente, desativada, precisa de uma licença que não está ativa ou não foi carregada, o seu plugin não é carregado de forma alguma, e Preferências ▸ Plug-ins informa de qual plugin ele precisa e por quê ("Precisa do plug-in LSP Provider, que está desativado"). Um ciclo bloqueia todos os plugins que fazem parte dele. A mesma chave significa a mesma coisa noplugin.jsonde um plugin Python.
Atribuindo bibliotecas incluídas — thirdParty¶
Se o seu plugin embute ou vincula um componente de terceiros, declare-o em um array
opcional thirdParty, para que o editor possa honrar as obrigações de atribuição
dele:
"thirdParty": [
{ "name": "Qt WebEngine", "license": "LGPL-3.0",
"url": "https://doc.qt.io/qt-6/qtwebengine-index.html",
"file": "QtWebEngine-LGPLv3.txt" }
]
Cada entrada precisa de um name (entradas sem um são descartadas); license,
url e file são opcionais. As licenças são do seu plugin: distribua-as ao
lado dele, sob Plugins/Licenses/<your-plugin-id>/, onde file é o nome base. A
caixa de diálogo Sobre lista os componentes thirdParty de todos os plugins
instalados e aponta os usuários para aquela pasta; o núcleo nunca carrega os textos
de terceiros de um plugin por ele. (O HtmlPreviewPlugin incluído, que embute o
QtWebEngine + Chromium, é o exemplo de referência.)
4. Compile-o¶
Use o auxiliar de CMake do SDK:
find_package(MTEPluginSDK REQUIRED)
mte_add_plugin(NAME HelloWorldPlugin
SOURCES Src/HelloWorldPlugin.cpp
# TRANSLATIONS Translations/HelloWorld_en.ts ...
)
Requisitos de compilação¶
Um plugin nativo é carregado no processo do editor, então precisa ser compilado com uma cadeia de ferramentas binariamente compatível com a compilação do editor à qual ele se destina. Estes são os requisitos da versão atual (compilada com Qt 6.11.2):
| Windows | macOS | |
|---|---|---|
| Compilador | Visual Studio 2022, conjunto de ferramentas MSVC 14.44 ou um 14.x mais antigo (VS 2015–2022 compartilham uma ABI de C++); clang-cl também funciona. Builds com MinGW / GCC não carregam. |
Apple clang do Xcode (Xcode 27, Apple clang 21.0.0); apenas arm64 |
| Configuração de build | Release, runtime dinâmico (/MD, o padrão do CMake). Um plugin em Debug é recusado pelo carregador; um plugin com runtime estático (/MT) falha quando a memória cruza a fronteira do plugin |
Release; alvo de implantação 14.4 (o mesmo do editor) |
| Qt | Qt 6.11, kit msvc2022_64. Um plugin compilado contra uma versão menor 6.x anterior carrega; um compilado contra uma menor mais nova pode não carregar |
Qt 6.11, mesma arquitetura |
| Padrão C++ | C++20 (os cabeçalhos da API usam std::span e conceitos) |
C++20 |
| CMake | 3.16+ | 3.16+ |
| Plugin API | o MTE_PLUGIN_API_VERSION do SDK que veio com a versão do editor à qual você se destina |
igual |
Duas consequências que vale conhecer no Windows:
- O Visual C++ Redistributable instalado na máquina do usuário precisa ser pelo menos tão novo quanto o conjunto de ferramentas MSVC mais novo usado pelo editor ou por qualquer plugin carregado. Compilar seu plugin com um conjunto de ferramentas mais novo que o do editor eleva esse requisito para os seus usuários; compilar com o do editor ou com um mais antigo, não.
- A versão do SDK do Windows contra a qual você compila não afeta a compatibilidade com o editor; ela apenas decide quais APIs do Windows o seu plugin pode chamar, e o editor suporta o Windows 10 1809 e posteriores.
E no macOS:
- O editor é distribuído apenas para arm64; um plugin só para
x86_64não pode ser carregado (um binário universal serve). Mantenha o alvo de implantação em 14.4: um plugin compilado para um macOS mínimo mais novo é recusado pelo carregador do sistema em Macs mais antigos, e o runtime é a própria libc++ do sistema operacional, então não se coloca a questão do redistribuível. - Referencie os frameworks do Qt por
@rpath, para que o plugin pegue as cópias incluídas dentro doOctoEdit.app. Kits do Qt cujos frameworks carregam nomes de instalação absolutos (os do Homebrew, por exemplo) precisam deinstall_name_toolno plugin compilado antes de ser distribuído; caso contrário o plugin procura o Qt no caminho da máquina de compilação.
Nada disso vale para plugins Python: eles rodam em um processo separado e não compartilham interface binária com o editor.
5. Instale e execute¶
Ou compacte a biblioteca compilada + o plugin.json como <name>.mteplugin e
instale-o por Preferências ▸ Plug-ins ▸ Instalar um plug-in... (o pacote é validado —
manifesto, segurança do arquivo compactado e metadados embutidos no binário — antes
de qualquer coisa ser tocada), ou coloque os arquivos manualmente em um diretório de
plugins descoberto (veja a
Visão geral da arquitetura). Reinicie o editor;
ative-o em Preferências ▸ Plug-ins, se necessário.
Próximo¶
- Visão geral da Plugin API — ciclo de vida, tokens e a tabela completa de serviços
- Comandos e menus
- Painéis, abas e barra de status
- Páginas de configurações
- Eventos
- Traduções