Plugins
This conceptual guide covers plugin packaging and integration. For signatures generated directly from the XML-documented contracts, see the Plugin API Reference.
Built-in Features
wtmux includes built-in equivalents of popular tmux plugins:
- Prefix highlight (
#{prefix_highlight}) — Shows an inverse-video indicator when the prefix key is active - Session save/restore — Save and restore window/pane layouts (tmux-resurrect equivalent)
- Conditional format (
#{?client_prefix,yes,no}) — Conditional text based on terminal state
COM Plugin System
External plugins use COM interfaces and MSIX AppExtensions for a fully AOT-compatible, language-agnostic plugin architecture. Plugins are discovered automatically when installed as MSIX packages that declare the com.wtmux.plugin extension contract.
Plugin Package Requirements
- Implement the
IWtmuxComPluginCOM interface ({3A2F8E01-7B4C-4D5A-9E1F-0A2B3C4D5E01}) - Register as a COM server in the package manifest
- Declare the
com.wtmux.pluginAppExtension with aCLSIDproperty
MSIX Manifest Example
<Extensions>
<!-- Register the COM server -->
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="MyPlugin.exe">
<com:Class Id="{YOUR-PLUGIN-CLSID}" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
<!-- Declare as a wtmux plugin extension -->
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension Name="com.wtmux.plugin"
DisplayName="My Plugin"
Description="Description of my plugin"
PublicFolder="Public">
<uap3:Properties>
<CLSID>{YOUR-PLUGIN-CLSID}</CLSID>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
</Extensions>
COM Interface (C#)
The plugin contract consists of two interfaces. Declare them with the same GUIDs (until a contract package is available):
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;
[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)]
[Guid("3A2F8E01-7B4C-4D5A-9E1F-0A2B3C4D5E01")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public partial interface IWtmuxComPlugin
{
void GetName(out string name);
void Initialize(IWtmuxComPluginHost host);
void OnSessionCreated(string sessionName, int sessionId);
void OnSessionDestroyed(string sessionName, int sessionId);
void ExpandVariable(string variableName, string sessionName,
int windowIndex, string windowName, int paneIndex,
bool prefixActive, out string result);
void Shutdown();
}
[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)]
[Guid("3A2F8E01-7B4C-4D5A-9E1F-0A2B3C4D5E02")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public partial interface IWtmuxComPluginHost
{
void RegisterFormatVariable(string name);
void BindKey(byte key, string command);
void UnbindKey(byte key);
void SetOption(string option, string value);
void Log(string message);
}
// Additive v2 interface for mouse and root-table (no-prefix) key bindings.
// Query the host for it; the original IWtmuxComPluginHost is unchanged.
[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)]
[Guid("3A2F8E01-7B4C-4D5A-9E1F-0A2B3C4D5E03")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public partial interface IWtmuxComPluginHost2
{
void BindMouse(string mouseKey, string command); // e.g. "WheelUpPane", "MouseDown1Pane"
void UnbindMouse(string mouseKey);
void BindRootKey(byte key, string command); // no-prefix keyboard binding
void UnbindRootKey(byte key);
}
// Additive v3 interface for running wtmux commands from a plugin.
// Query the host for it; the earlier host interfaces are unchanged.
[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)]
[Guid("3A2F8E01-7B4C-4D5A-9E1F-0A2B3C4D5E04")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public partial interface IWtmuxComPluginHost3
{
// Runs a wtmux command in the server's global context (use -t for targeting).
void RunCommand(string command, out string output, out bool success);
}
To bind mouse actions or no-prefix keys, cast (query) the host to IWtmuxComPluginHost2:
public void Initialize(IWtmuxComPluginHost host)
{
host.BindKey((byte)'T', "new-window");
if (host is IWtmuxComPluginHost2 host2)
{
host2.BindMouse("MouseDown2Pane", "new-window"); // middle-click opens a window
host2.BindRootKey((byte)0x0F, "copy-mode"); // Ctrl+O (no prefix)
}
}
To run wtmux commands, cast (query) the host to IWtmuxComPluginHost3:
public void Initialize(IWtmuxComPluginHost host)
{
if (host is IWtmuxComPluginHost3 host3)
{
host3.RunCommand("set-option -g status-right \"#{my_var}\"", out var output, out var ok);
if (!ok) host.Log($"command failed: {output}");
}
}
Plugin-issued commands run in the server's global context — there is no attached client — so
client-independent commands work: global option changes (set-option -g), config commands
(source-file/reload-config), key bindings (bind/unbind), and queries (show-option).
Commands that act on the current client/session (new-window, split-window, display-message,
send-keys, …) are not supported from this context yet and return a failure. Avoid calling
RunCommand reentrantly from inside a host event callback.
A minimal implementation:
[GeneratedComClass]
public partial class MyPlugin : IWtmuxComPlugin
{
public void GetName(out string name) => name = "my-plugin";
public void Initialize(IWtmuxComPluginHost host)
{
host.RegisterFormatVariable("my_var");
host.BindKey((byte)'T', "new-window");
host.Log("My plugin loaded!");
}
public void ExpandVariable(string variableName, string sessionName,
int windowIndex, string windowName, int paneIndex,
bool prefixActive, out string result)
{
result = variableName == "my_var"
? (prefixActive ? "[PREFIX]" : "[NORMAL]")
: "";
}
public void OnSessionCreated(string sessionName, int sessionId) { }
public void OnSessionDestroyed(string sessionName, int sessionId) { }
public void Shutdown() { }
}
Architecture Benefits
- AOT-compatible — No reflection or assembly loading; uses source-generated COM interop
- Language-agnostic — Plugins can be written in C#, C++, Rust, or any COM-capable language
- Process isolation — Out-of-process COM servers prevent plugin crashes from affecting wtmux
- Automatic discovery — MSIX AppExtensions handle plugin enumeration; no manual folder scanning
- Secure — MSIX packages are signed and sandboxed
- Store-distributable — Plugins can be published and updated via the Microsoft Store
Format Variable Context
When wtmux renders a plugin's #{variable}, it provides the context of the pane/window being
rendered. Two levels are available:
IWtmuxComPlugin.ExpandVariablereceives the session name, window index/name, pane index, and prefix state.IWtmuxComPlugin2.ExpandVariable2(additive) receives the full context — window flags, pane title, pane pid, pane current path, session window count, window activity, and host name — so a plugin can build real status segments (a git indicator frompaneCurrentPath, a process indicator frompanePid/paneTitle, and so on). ImplementIWtmuxComPlugin2in addition toIWtmuxComPlugin; the host callsExpandVariable2when present and falls back toExpandVariableotherwise, so plugins that implement onlyIWtmuxComPluginkeep working.
[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)]
[Guid("3A2F8E01-7B4C-4D5A-9E1F-0A2B3C4D5E05")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public partial interface IWtmuxComPlugin2
{
void ExpandVariable2(string variableName, string sessionName, int windowIndex,
string windowName, string windowFlags, int paneIndex, string paneTitle, int panePid,
string paneCurrentPath, int sessionWindows, string windowActivity, string host,
bool prefixActive, out string result);
}
In-process built-in providers registered via RegisterFormatVariable(name, ctx => …) receive the
same enriched FormatContext (with PaneCurrentPath, PanePid, PaneTitle, WindowFlags,
SessionWindows, WindowActivity, and Host).
Plugin Configuration (@ User Options)
Plugins are configured through tmux-style @ user options. A user sets, for example,
set -g @my_plugin_theme dark in their config (or wtmux set-option -g @my_plugin_theme dark at
runtime). A plugin reads its value through the in-process host's GetUserOption("@my_plugin_theme"),
from a format string as #{@my_plugin_theme}, and can write one with
SetOption("@my_plugin_theme", "dark"). See the user options
section of the configuration reference.
PowerShell Config Scripts
Use run-shell in your config to execute PowerShell scripts that output wtmux commands:
# In ~/.wtmux.conf
run 'Write-Output "set -g status-right $(Get-Date -Format HH:mm)"'