> ## Documentation Index
> Fetch the complete documentation index at: https://docs.otter-shell.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Writing plugins

> C ABI for bar widgets and launcher providers, plus opf to scaffold and pack them.

Bar and launcher load third-party widgets as `.so` files. A plugin does not draw pixels. It emits Surface Description nodes through a small C ABI, and the host renders them.

User-facing install, enable toggles, and the example plugins: [Plugins](/desktop/plugins). This page is the write-your-own side.

`otter-shell` already ships `otter-shell-plugins` (examples) and `otter-plugin-factory` (`opf`). You can also install them on their own:

```bash theme={null}
pikman install otter-shell-plugins otter-plugin-factory
```

## Layout and discovery

Packed plugins look like this:

```
/usr/lib/otter-shell/plugins/<id>/
  plugin.conf
  libotter_plugin_<snake_id>.so
```

Hosts search in this order. First match per id wins:

1. `OTTER_PLUGIN_PATH` (colon-separated roots)
2. `$XDG_DATA_HOME/otter-shell/plugins`
3. `~/.local/share/otter-shell/plugins`
4. `/usr/lib/otter-shell/plugins`
5. `/usr/share/otter-shell/plugins`

Bar and launcher layout names are always `plugin:<id>`. The bare id lives in `plugin.conf`.

Enable gates live in `~/.config/otter-shell/plugins.conf` as `<id>_enabled = true|false`. Hosts read that file before `dlopen`. Most plugins treat a missing key as enabled. The packaged `snippet-provider` is opt-in and defaults to disabled. Per-plugin files under `~/.config/otter-shell/plugins/<id>.conf` are reserved for schemas with `provides_settings = true`. That settings UI is not in yet.

## Manifest (`plugin.conf`)

```conf theme={null}
id = "hello-label"
name = "Hello label"
version = "0.1.1"
abi_version = 1
library = "libotter_plugin_hello_label.so"
provides_bar_widget = true
provides_launcher_provider = false
provides_launcher_sd_rows = false
provides_settings = false
```

## Factory CLI (`opf`)

[`otter-plugin-factory`](https://git.pika-os.com/otter-shell/otter-plugin-factory) is a separate package from the examples. After install, `opf` is on `PATH`. From a checkout, put `bin/` on `PATH` instead.

```bash theme={null}
opf new bar-widget my-chip
opf new launcher-provider my-provider

cd my-chip
opf build
opf pack                 # zig-out/plugins/<id>/{plugin.conf,.so}
opf check                # manifest + otter_plugin_* exports
```

Copy `zig-out/plugins/<id>/` into `~/.local/share/otter-shell/plugins/` or set `OTTER_PLUGIN_PATH`. Factory `docs/publishing.md` covers the examples-package layout.

## ABI v1

Plugins export C symbols. Zig plugins can `@import` a mirrored `otter_plugin_abi` module. ABI major is `OTTER_PLUGIN_ABI_VERSION` (`1`).

Required exports:

| Symbol                                      | Role                       |
| ------------------------------------------- | -------------------------- |
| `otter_plugin_abi_version`                  | Must return `1`            |
| `otter_plugin_init` / `otter_plugin_deinit` | Lifetime                   |
| `otter_plugin_query`                        | Fill `OtterPluginManifest` |

Bar, when `provides_bar_widget` is true:

* `otter_plugin_bar_attach` returns an `OtterPluginBarInstance` with a vtable
* Vtable: `preferred_width`, `contribute`, `destroy`, optional `on_event`

Launcher, when provider or SD-row flags are set:

* `otter_plugin_launcher_query` / `otter_plugin_launcher_activate`
* Optional `otter_plugin_launcher_contribute_row` for custom SD rows

Host API during contribute:

* `sd_rect` / `sd_label` / `sd_hit_button`
* Optional `sd_text_input` for IME-backed fields. Null-check on older hosts.
* `request_frame`, `log`

`BarVTable.on_event` can receive click, hover, focus/blur, and text-input (buffer/preedit flags).

Headers and Zig mirrors live in `otter-bar`, `otter-launcher`, and `otter-shell-plugins` (`include/otter_plugin_abi.h`, `abi/abi.zig`).

## Examples in `otter-shell-plugins`

| Layout id                 | Host     | What it shows                                                   |
| ------------------------- | -------- | --------------------------------------------------------------- |
| `plugin:hello-label`      | bar      | Smallest click chip. `on_event` toggles Hello and Clicked.      |
| `plugin:bar-search`       | bar      | `sd_text_input` plus text-input-v3 IME (preedit, commit, focus) |
| `plugin:cmd-watch`        | bar      | Interval command to a label                                     |
| `plugin:disk-free`        | bar      | `statfs` free space plus a usage bar                            |
| `plugin:idle-inhibit`     | bar      | logind idle inhibit toggle on click                             |
| `plugin:snippet-provider` | launcher | Results provider plus custom SD rows                            |

### hello-label

Smallest SD chip that gets a real click from the host:

`bar_input_dispatch` to the plugin slot to `BarVTable.on_event(CLICK)`.

Left click toggles the label text (Hello / Clicked) and background tint, then calls `host.request_frame`. No side-channel file.

```conf theme={null}
# otter-bar.conf
layout_right = plugin:hello-label, clock
```

### bar-search

Bar text field using host `sd_text_input` with Wayland text-input-v3. Focus the field, type, and confirm IME preedit/commit. Escape blurs. `$XDG_RUNTIME_DIR/otter-bar-search` is fallback only on old hosts.

```conf theme={null}
layout_center = plugin:bar-search
```

## Local development

```bash theme={null}
git clone https://git.pika-os.com/otter-shell/otter-shell-plugins.git
cd otter-shell-plugins
zig build -Doptimize=ReleaseFast pack
export OTTER_PLUGIN_PATH="$PWD/zig-out/plugins"
```

ABI smoke, no Wayland:

```bash theme={null}
zig build -Doptimize=ReleaseFast plugin-abi-smoke -- zig-out/plugins/hello-label/libotter_plugin_hello_label.so
```

Bar plugins: use `otter-bar`'s `zig build -Doptimize=ReleaseFast plugin-abi-smoke -- <so>`.

## Trust

Installed `.so` plugins are fully trusted and share the host process. Ship examples through `otter-shell-plugins`, or load plugins you built yourself.

## Related repos

* [otter-shell-plugins](https://git.pika-os.com/otter-shell/otter-shell-plugins): examples package source
* [otter-plugin-factory](https://git.pika-os.com/otter-shell/otter-plugin-factory): `opf` CLI and agent skills
* [otter-zenith](https://git.pika-os.com/otter-shell/otter-zenith): Debian packaging
* [otter-bar](https://git.pika-os.com/otter-shell/otter-bar) / [otter-launcher](https://git.pika-os.com/otter-shell/otter-launcher): host runtimes
