Keybinds
A plugin can bind keyboard shortcuts to actions. Bindings are stored as strings
(e.g. "Ctrl+F;F2") so users can rebind them, and they show up in the shared
keybind UI. A keybind has three concerns:
- A binding string member, loaded/saved like any other setting.
- A keybind entry registered so the UI can display and edit it.
- The actual binding to a handler, done in
OnBindHotkeys.
The declarative way
ModuleBase's Keybinds() table wires all three at once: it loads and seeds the
string, registers the Settings → Keybinds row, and binds the handler. This is the
recommended approach:
class MyPlugin : public ModuleBase
{
std::string toggleKey_ = "Ctrl+M";
bool showPanel_ = true;
protected:
const char* ModuleName() const override { return "MyPlugin"; }
std::vector<framelift::Keybind> Keybinds() override
{
return {
{"Toggle my panel", "togglePanel", &toggleKey_, "Ctrl+M",
[this] { showPanel_ = !showPanel_; }},
};
}
};
Each Keybind is {label, action, &storage, default, onPress}:
- label — text shown in the Settings → Keybinds list.
- action — a stable identifier for the binding.
- storage — the
std::stringmember holding the current bind string. - default — the seed bind list when nothing is saved yet.
- onPress — the handler; leave empty to register a row without binding.
The manual way
If you need finer control, drive the three legs yourself. Keybind strings live in
the shared keybinds section, not your settings section. Use
LoadKeybinds/SaveKeybinds and namespace your keys with PrefixedKey so they
never collide with another plugin's:
void LoadKeybinds(IModuleSettings& ks) override
{
toggleKey_ = ks.GetString(PrefixedKey("toggle").c_str(), "Ctrl+M");
}
void SaveKeybinds(IModuleSettings& ks) override
{
ks.SetString(PrefixedKey("toggle").c_str(), toggleKey_.c_str());
}
PrefixedKey("toggle") yields "MyPlugin.toggle".
Registering the entry for the UI
Override RegisterKeybinds and register an entry backed by your string member.
The <framelift/ContextHelpers.h> helper writes the get/set glue for you:
#include <framelift/ContextHelpers.h>
void RegisterKeybinds(IModuleContext& ctx) override
{
framelift::RegisterKeybindEntry(ctx, "Toggle my panel", "MyPlugin.toggle", toggleKey_);
}
Binding to a handler
Override OnBindHotkeys. It runs once, after all plugins are installed, so you
can safely reference services other plugins registered. Use the framelift::Bind
helpers from <framelift/HotkeyHelpers.h>, which accept a lambda:
#include <framelift/HotkeyHelpers.h>
void OnBindHotkeys(Hotkeys& keys) override
{
// Named, rebindable binding driven by the stored string.
framelift::Bind(keys, "MyPlugin.toggle", toggleKey_, [this] {
showPanel_ = !showPanel_;
});
}
Bind has overloads for:
- Named bindings from a bind-list string (
"Ctrl+F;F2"— first entry is the rebindable name, the rest are aliases). This is what pairs with a registered keybind entry. - Unnamed bindings to a fixed
Key(+ optionalMod) from<framelift/AppEvent.h>, e.g.framelift::Bind(keys, Keys::Space, [this] { ... }).
Each binding heap-allocates its closure and registers a cleanup callback, so the memory is released automatically when your plugin unloads.
Handling raw key events instead
For one-off keyboard handling that does not need to be user-rebindable, override
HandleKeyDownEvent instead of registering a hotkey. Return true to consume the
event and stop further dispatch:
bool HandleKeyDownEvent(const AppEvent& e) override
{
const auto& k = e.AsKey();
if (k.key == Keys::Escape && k.mods == Mod::None)
{
Close();
return true; // consumed
}
return false;
}
See AppEvent for the Key/Mod constants and
the payload accessors.