Using plugins
Where the plugin directory lives, how to enable plugins, configuration key naming, and the built-in plugins shipped with every Fancy Mumble image. See Using plugins.
Fancy Mumble extends the classic Mumble server with a Rust plugin
host. Features like the persistence-backed file store, the
collaborative live-doc editor, screen-share signalling glue, and the
push-notification bridge are all built as cdylib plugins loaded at
startup from a directory on disk.
The host is intentionally loosely coupled: the server does not know about specific plugins. A plugin registers itself by being present in the plugin directory, advertises its capabilities to clients at connect time, and receives addressed messages through a single generic envelope.
The server loads libmumble_plugin_host.so once at boot. That cdylib
scans every configured plugin directory, dlopens each entry, and
exposes a single C ABI back to the C++ server. Every Mumble plugin is
itself a cdylib that depends on the mumble-plugin-api crate.
Plugins do not get their own protobuf message types. Instead, the client and server speak two generic envelopes:
| Wire ID | Name | Direction | Purpose |
|---|---|---|---|
200 | PluginMessage | Bidirectional | Addressed plugin payload. |
201 | PluginRegistry | Server -> Client | Announces loaded plugins after ServerSync. |
A PluginMessage carries:
plugin_name (string) - the stable identifier of the target plugin.payload_type (string) - a plugin-defined inner discriminator
("OpenRequest", "Invite", "Vote", …).payload (bytes) - opaque bytes; each plugin picks its own encoding
(JSON, protobuf, MessagePack, …).target_sessions (repeated u32) - explicit recipient sessions.channel_id (optional u32) - channel-scoped fan-out hint.sender_session / sender_name - stamped by the server on inbound.The server routes inbound envelopes to exactly one plugin (the one
whose name() matches plugin_name). On outbound, the server delivers
to every session in target_sessions, or - if that list is empty - to
every member of channel_id. Clients that report a Fancy version
older than 0.4.0 are skipped.
Right after the server delivers ServerSync to a connecting client,
it sends a PluginRegistry listing every loaded plugin:
{ "plugins": [ { "plugin_name": "fancy-live-doc", "version": "0.1.0", "plugin_slot": 0, "info_json": "{...}" }, { "plugin_name": "fancy-file-server", "version": "0.1.0", "plugin_slot": 1, "info_json": "{...}" } ]}The info_json blob is whatever the plugin returned from
MumblePlugin::info_json(). For Fancy-built plugins this is a typed
PluginInfo struct serialised as JSON, hard-capped at 64 KiB. Clients
use it to populate the developer Server Info panel and to decide
whether a feature is available on this server.
Plugins are loaded across an abi_stable-based FFI boundary
(abi_stable on crates.io).
That means:
rustc version than the
host and still load cleanly.abi_version does not
match PLUGIN_ABI_VERSION (currently 2). When the trait surface
changes, that constant is bumped and every plugin must be rebuilt.RString, RSlice,
RVec, ROption, RArc) instead of their std counterparts.Each plugin owns its own private tokio runtime. A plugin
panicking inside an async task only takes down its own runtime; the
host catches the panic at the FFI boundary, logs an error, and the
rest of the server continues running. Other plugins are unaffected.
Using plugins
Where the plugin directory lives, how to enable plugins, configuration key naming, and the built-in plugins shipped with every Fancy Mumble image. See Using plugins.
Develop your own plugin
A complete walk-through of building a cdylib plugin from scratch
in Rust, registering it with the host, and round-tripping a
message with the client. See Developing a plugin.