> ## 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.

# Create a new Otter Shell app

> Start a Wayland app on the Otter stack: dependencies, UI patterns, and config.

Build a new Wayland app on the Otter Shell stack.

## 1. Create the project

Initialize a Zig project and add Otter dependencies in `build.zig.zon`:

```zig theme={null}
.{
    .name = .my_otter_app,
    .version = "0.1.0",
    .minimum_zig_version = "0.16.0",
    .dependencies = .{
        .otter_ui = .{ .path = "../otter-ui" },
        .otter_wayland = .{ .path = "../otter-wayland" },
        .otter_render = .{ .path = "../otter-render" },
        .otter_theme = .{ .path = "../otter-theme" },
        .otter_conf = .{ .path = "../otter-conf" },
        .otter_config_types = .{ .path = "../otter-config-types" },
        .otter_utils = .{ .path = "../otter-utils" },
        // Add otter_desktop if you need D-Bus, icons, or .desktop parsing.
    },
    .paths = .{
        "build.zig",
        "build.zig.zon",
        "src",
    },
}
```

Outside the monorepo, fetch libraries by URL:

```bash theme={null}
zig fetch --save git+https://git.pika-os.com/otter-shell/otter-conf
```

## 2. Wire imports in build.zig

```zig theme={null}
const otter_ui = b.dependency("otter_ui", .{
    .target = target,
    .optimize = optimize,
});
const otter_wayland = b.dependency("otter_wayland", .{
    .target = target,
    .optimize = optimize,
});
const otter_render = b.dependency("otter_render", .{
    .target = target,
    .optimize = optimize,
});

const imports = [_]std.Build.Module.Import{
    .{ .name = "otter_ui", .module = otter_ui.module("otter_ui") },
    .{ .name = "otter_wayland", .module = otter_wayland.module("otter_wayland") },
    .{ .name = "otter_render", .module = otter_render.module("otter_render") },
};

const exe = b.addExecutable(.{
    .name = "my-otter-app",
    .root_module = b.createModule(.{
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
        .imports = &imports,
    }),
});
```

## 3. Choose your app pattern

Otter apps differ in two ways: **which Wayland surface you create**, and **how long the process runs**. A daemon might use layer shell, an XDG toplevel, or no window at all. Do not treat "daemon" as a synonym for layer shell.

### Layer-shell surfaces (`zwlr_layer_shell_v1`)

Use `otter-wayland` layer shell when the compositor should anchor your surface to an output edge or layer, not as a normal floating window.

| Layer                   | Lifecycle                         | Examples                                                         |
| ----------------------- | --------------------------------- | ---------------------------------------------------------------- |
| `.top` / `.bottom` bar  | Daemon                            | `otter-bar`                                                      |
| `.background`           | Daemon                            | `otter-wallpaper`                                                |
| `.overlay` (persistent) | Daemon                            | `otter-notifications`, `otter-osd`, `otter-transcribe` indicator |
| `.overlay` (on demand)  | Overlay session, exits on dismiss | `otter-launcher`, `otter-logout`                                 |
| `.overlay` (on demand)  | Daemon shows dialog when needed   | `otter-polkit`                                                   |
| `.overlay` (fullscreen) | Daemon while greeter runs         | `otter-greeter-ui`                                               |
| `.top` popup            | Optional CLI flag                 | `otter-weather --layer`, `otter-note popup`                      |

Common overlay pattern: `DimOverlay` (fullscreen dim on `.overlay`) plus a second layer surface for the panel. See `otter-launcher` and `otter-logout`. Overlay sessions that exit after one action often use `ArenaAllocator`.

Layer-shell daemons typically poll Wayland plus D-Bus, inotify config watchers, and timer FDs. Use `GeneralPurposeAllocator` and `otter-conf.Watcher` for hot reload.

### XDG toplevel windows (`xdg_toplevel`)

Use `otter-wayland` `XdgToplevel` for regular desktop windows with a title bar and workspace placement.

| Lifecycle                                         | Examples                      |
| ------------------------------------------------- | ----------------------------- |
| Singleton window (second launch focuses existing) | `otter-settings`              |
| Long-running while open                           | `otter-term`, `otter-monitor` |
| Runs until export or quit                         | `otter-shot` composer         |

Describe UI with **Surface Description** (`otter-ui` `UiFrame`). Poll Wayland plus app-specific FDs (PTY for `otter-term`, config inotify, keyboard repeat).

### Session lock (`ext_session_lock_v1`)

`otter-lock` draws on lock surfaces from `ext_session_lock_v1`, not layer shell. The process stays up while the session is locked.

### Capture and selection overlays

Region pickers and frozen-capture UIs use shared `otter-wayland` helpers (`selection_overlay`, `capture_overlay`, `dim_overlay`). They attach layer `.overlay` surfaces but are not full apps.

| Lifecycle                            | Examples                                       |
| ------------------------------------ | ---------------------------------------------- |
| One-shot CLI                         | `otter-screenshot` (region mode), `otter-pick` |
| Toggle CLI                           | `otter-rec` (`--region select`)                |
| Transient step inside a toplevel app | `otter-shot` dragged selection                 |

### No Wayland UI

| Lifecycle                      | Examples                                                                  |
| ------------------------------ | ------------------------------------------------------------------------- |
| Daemon (protocols or IPC only) | `otter-idle`, `otter-clip daemon`, `otter-greeterd`                       |
| One-shot CLI                   | `otter-calc`, `otter-emoji`, `otter-cal`, `otter-vox`, `otter-timer`      |
| CLI with optional layer popup  | `otter-weather` (default prints to stdout; `--layer` opens a layer popup) |

CLI tools can use minimal deps (`otter-tools-core` + `otter-conf`) with no Wayland connection unless you add a popup mode.

### Quick reference

| You want…             | Surface                                | Study                                                         |
| --------------------- | -------------------------------------- | ------------------------------------------------------------- |
| Status bar            | Layer `.top` / `.bottom`               | `otter-bar`                                                   |
| Wallpaper             | Layer `.background`                    | `otter-wallpaper`                                             |
| Toast notifications   | Layer `.overlay` per notification      | `otter-notifications`                                         |
| Launcher / power menu | Layer `.overlay` + dim, exit on action | `otter-launcher`, `otter-logout`                              |
| Settings GUI          | `xdg_toplevel` singleton               | `otter-settings`                                              |
| Terminal              | `xdg_toplevel`                         | `otter-term`                                                  |
| Lock screen           | `ext_session_lock_v1`                  | `otter-lock`                                                  |
| Region screenshot     | `selection_overlay`                    | `otter-screenshot`, `otter-wayland/src/selection_overlay.zig` |
| Expression in a pipe  | No surface                             | `otter-calc`                                                  |
| Idle / DPMS only      | No surface                             | `otter-idle`                                                  |

## 4. Build the UI with Surface Description

Use Surface Description via `UiState` and `UiFrame`. The [otter-ui guide](/developers/libraries/otter-ui) covers layout, nodes, input, and examples.

```zig theme={null}
const ui = @import("otter_ui");
const render = @import("otter_render");

const caps = ui.ui_frame.Capacities{ .elements = 64, .hit_regions = 64, .overlays = 8 };
var state: ui.UiState(caps) = .{};

var text_system = try render.text.TextSystem.init(allocator, font);
defer text_system.deinit();
var text_scratch: render.text.ShapeScratch = .{};

var frame = state.begin(.{
    .viewport = .{ .x = 0, .y = 0, .width = width, .height = height },
    .scale = surface.scale,
    .font = font,
    .text_system = &text_system,
    .text_scratch = &text_scratch,
});

const root = ui.SurfaceNode{
    .id = ui.SurfaceId.namedComptime("root"),
    .kind = .column,
    .layout = .{ .width = .fill, .height = .fill, .gap = 8, .padding = ui.Padding.uniform(12) },
    .children = &children,
};

_ = try frame.render(&root, frame.viewport);
try frame.finish();

// Rasterize via otter-render (commands inherit text_system from the frame)
state.rasterize(surface, null, true);
```

Use `state.hitTest(point)` for pointer input. No parallel hit arrays needed.

Skip `text_system` and `text_scratch` for ASCII-only labels. Pass both when the UI might show RTL, CJK, or other user-typed text (see `otter-lock/src/lock_render_loop.zig`).

## 5. Add configuration

Define a config struct and parse it with `otter-conf`:

```zig theme={null}
const otter_conf = @import("otter_conf");

const Config = struct {
    font_size: u16 = 14,
    enabled: bool = true,
    label: []const u8 = "Hello",
};

var config = try otter_conf.load(Config, allocator, config_path, .{});
defer otter_conf.freeConfig(Config, allocator, &config);
```

Store config at `~/.config/otter-shell/my-app.conf`. On first run, normalize the file (add missing defaults, strip unknown keys).

For typed configs shared with `otter-settings`, add structs to `otter-config-types` and register a tab in `otter-settings/src/tabs.zig`.

## 6. Apply theming

Load the shared theme:

```zig theme={null}
const theme = try otter_theme.loader.loadTheme(allocator);
defer theme.deinit(allocator);
```

Widget config fields should use `?Color = null` and resolve via `config.field orelse theme.token`. Precedence: widget config, then `theme.conf`, then compiled defaults.

## 7. Install a .desktop file

For launcher integration, install a freedesktop `.desktop` entry:

```ini theme={null}
[Desktop Entry]
Name=My App
Exec=my-otter-app
Icon=my-otter-app
Type=Application
Categories=Utility;
```

Place in `data/applications/` and install icons under `data/icons/hicolor/`.

## Reference implementations

| Pattern                    | Study these sources                                                        |
| -------------------------- | -------------------------------------------------------------------------- |
| Layer bar daemon           | `otter-bar/src/main.zig`, `otter-bar/src/config.zig`                       |
| Layer background daemon    | `otter-wallpaper/src/main.zig`, `otter-wallpaper/src/output.zig`           |
| Layer overlay session      | `otter-launcher/src/main.zig`, `otter-logout/src/main.zig`                 |
| Layer overlay daemon       | `otter-notifications/src/main.zig`, `otter-osd/src/main.zig`               |
| XDG toplevel window        | `otter-settings/src/app.zig`, `otter-term/src/main.zig`                    |
| Session lock               | `otter-lock/src/main.zig`                                                  |
| Region capture / selection | `otter-screenshot/src/main.zig`, `otter-wayland/src/selection_overlay.zig` |
| Toplevel + selection step  | `otter-shot/src/main.zig`, `otter-shot/src/capture/workflow.zig`           |
| CLI helper                 | `otter-calc/src/main.zig`, `otter-tools-core/src/calc.zig`                 |
| No-surface daemon          | `otter-idle/src/main.zig`, `otter-clip/src/main.zig`                       |

See the [Libraries overview](/developers/libraries) and per-library pages in the sidebar for API details.
