Plugin Lifecycle
A module is a class that implements IModule. In
practice you derive from ModuleBase, which provides ordered scaffolding and
a set of named hooks to override. This page covers the full lifecycle and when
each hook runs.
IModule vs ModuleBase
IModule is deliberately tiny — only the lifecycle the host always calls:
class IModule
{
public:
static constexpr const char* InterfaceId = "framelift.IModule";
virtual ~IModule() = default;
virtual void Install(IModuleContext& ctx) noexcept {}
virtual void Uninstall() noexcept {}
virtual void* QueryInterface(const char* interfaceId) noexcept { return nullptr; }
};
A module opts into the host's other dispatch surfaces by implementing small
secondary interfaces — IHotkeyProvider, IEventHandler, IMediaEventHandler,
IShutdownHandler. You rarely implement these by hand: ModuleBase implements
all of them, seals Install()/Uninstall()/BindHotkeys()/OnEvent()/etc.
so the standard scaffolding (storing the context, loading settings, registering
keybinds) always runs correctly, and exposes named hooks for your code instead.
Hooks, in order
When the host installs a ModuleBase module, Install() runs this sequence:
- Stores the context pointer in
ctx_(available to all later hooks). - Calls
LoadSettings(ps)— read your fields from your INI section. - Calls
RegisterKeybinds(ctx)— declare keybind entries for the UI (the default registers theKeybinds()table). - Calls
OnInstall(ctx)— your main setup.
Later, separately:
BindHotkeys→OnBindHotkeys(keys)— bind action handlers, after all modules are installed.HandleMediaEvent,HandleKeyDownEvent, render hooks — during the main loop.HandleShutdown— once, after the main loop exits.Uninstall→OnUninstall— on unload; the host also clears your subscriptions and hotkeys here.
The hooks you override
| Hook | Purpose |
|---|---|
const char* ModuleName() | Required. Identifies the module: INI section, settings page title, log label. |
OnInstall(IModuleContext&) | Main setup: register services and a QML settings page, subscribe to events, add context-menu items. |
Keybinds() | Return a table of keybinds (declares storage, default, UI row, and handler at once). |
LoadSettings(IModuleSettings&) | Read member fields from your INI section. |
SaveSettings(IModuleSettings&) | Write member fields back on Save. |
RegisterKeybinds(IModuleContext&) | Register keybind entries shown in the keybind UI. Default consumes Keybinds(). |
LoadKeybinds / SaveKeybinds | Read/write keybind strings from the shared keybinds section. |
OnBindHotkeys(Hotkeys&) | Bind each keybind to its handler. |
HandleMediaEvent(const MediaEvent&) | React to the player. See Media Events. |
HandleKeyDownEvent(const AppEvent&) | Handle a key press; return true to consume it. |
HandleShutdown() | Teardown after the loop exits (e.g. apply a pending update). |
OnUninstall() | Final teardown on unload. |
A minimal module overrides only ModuleName() and OnInstall(). Everything else
has an empty default. Persist your own state with LoadSettings/SaveSettings,
and declare keybinds with Keybinds() — see Settings and
Keybinds.
A typical OnInstall
void OnInstall(IModuleContext& ctx) override
{
// Look up another plugin's service (null if that plugin isn't loaded).
if (auto* history = ctx.GetService<IHistory>())
resumePos_ = history->GetResumePos(lastPath_);
// React to an app-wide event. Subscribe() is a free helper in
// <framelift/ContextHelpers.h> that wraps a lambda over the POD ABI.
framelift::Subscribe<FileOpenedEvent>(ctx, [](const FileOpenedEvent& e) {
Log::Info("[MyPlugin] now playing {}", e.path);
});
// Register a QML settings page (own the view-model for the app's lifetime).
if (auto* pages = ctx.GetService<ISettingsPageRegistry>())
{
settingsPage_ = std::make_unique<MyPluginSettings>(*this);
pages->RegisterSettingsPage("myplugin", "My Plugin",
"qrc:/qt/qml/FrameLift/Plugins/MyPlugin/MyPluginSettings.qml",
settingsPage_.get(), 330);
}
}
See Cross-Plugin Communication for GetService
and Subscribe, and Settings for registering a settings page.
The ctx_ member
ModuleBase stores the context as IModuleContext* ctx_. After Install, any
hook can use it — for example to discover a service or publish an event — without
it being passed in again.
Shutdown
HandleShutdown() runs once, after the main loop drains its remaining events.
Use it for teardown that must happen late. The host also clears your
subscriptions and hotkeys on unload, invoking the cleanup callbacks the SDK
helpers registered, so you do not have to unsubscribe manually.