Skip to main content

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:

  1. Stores the context pointer in ctx_ (available to all later hooks).
  2. Calls LoadSettings(ps) — read your fields from your INI section.
  3. Calls RegisterKeybinds(ctx) — declare keybind entries for the UI (the default registers the Keybinds() table).
  4. Calls OnInstall(ctx) — your main setup.

Later, separately:

  1. BindHotkeysOnBindHotkeys(keys) — bind action handlers, after all modules are installed.
  2. HandleMediaEvent, HandleKeyDownEvent, render hooks — during the main loop.
  3. HandleShutdown — once, after the main loop exits.
  4. UninstallOnUninstall — on unload; the host also clears your subscriptions and hotkeys here.

The hooks you override

HookPurpose
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 / SaveKeybindsRead/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.