---
name: niri-window-capture
description: Invisibly capture screenshots of any window across all workspaces using niri compositor
---

# Niri Window Capture

⚠️ **SECURITY NOTICE**: This skill can capture ANY window invisibly, including windows on other workspaces. All captures are logged to systemd journal. See [SECURITY.md](references/SECURITY.md) for details.

Capture screenshots of windows from any workspace without switching views or causing visual changes. Uses niri's direct window rendering capability to access window buffers invisibly.

## When to Use

Invoke this skill when the user requests:
- "Show me what's in the focused window"
- "Capture the Firefox window"
- "Show me window X"
- "Find the window with [content]" (capture all, analyze)
- "What's on workspace 2?" (capture windows from specific workspace)

## How It Works

niri compositor maintains window buffers for all windows regardless of workspace visibility. The `screenshot-window` action renders individual windows directly without compositing to screen.

**Key insight**: Windows from inactive workspaces CAN be captured invisibly because their buffers exist in memory even when not displayed.

## Helper Scripts

### capture-focused.sh

**Purpose**: Capture the currently focused window

**Usage**:
```bash
./scripts/capture-focused.sh
```

**Output**: Path to screenshot file in `~/Pictures/Screenshots/`

**Example**:
```bash
SCREENSHOT=$(./scripts/capture-focused.sh)
# Now analyze: "What's in this screenshot?"
```

### capture-by-title.sh

**Purpose**: Find and capture window by partial title match (case-insensitive)

**Usage**:
```bash
./scripts/capture-by-title.sh "search-term"
```

**Output**: Path to screenshot file

**Example**:
```bash
# Capture any Firefox window
SCREENSHOT=$(./scripts/capture-by-title.sh "Firefox")

# Capture terminal with specific text in title
SCREENSHOT=$(./scripts/capture-by-title.sh "error")
```

## Direct niri Commands

For custom workflows, use niri commands directly:

**List all windows**:
```bash
niri msg --json windows | jq -r '.[] | "\(.id) - \(.title) - WS:\(.workspace_id)"'
```

**Capture specific window by ID**:
```bash
niri msg action screenshot-window --id <WINDOW_ID> --write-to-disk true
# Screenshot saved to ~/Pictures/Screenshots/
```

**Get focused window**:
```bash
niri msg --json focused-window | jq -r '.id'
```

## Common Workflows

### Find window with specific content

```bash
# Get all window IDs
WINDOW_IDS=$(niri msg --json windows | jq -r '.[].id')

# Capture each window
for id in $WINDOW_IDS; do
    niri msg action screenshot-window --id "$id" --write-to-disk true
    sleep 0.1
    SCREENSHOT=$(ls -t ~/Pictures/Screenshots/*.png | head -1)
    # Analyze screenshot for content
    # If found, return this one
done
```

### Capture all windows on specific workspace

```bash
# Get windows on workspace 2
WINDOW_IDS=$(niri msg --json windows | jq -r '.[] | select(.workspace_id == 2) | .id')

# Capture each
for id in $WINDOW_IDS; do
    niri msg action screenshot-window --id "$id" --write-to-disk true
    sleep 0.1
done
```

### Capture window by app_id

```bash
# Find Firefox window
WINDOW_ID=$(niri msg --json windows | jq -r '.[] | select(.app_id == "firefox") | .id' | head -1)

# Capture it
niri msg action screenshot-window --id "$WINDOW_ID" --write-to-disk true
```

## Guidelines

1. **No visual disruption**: All captures are invisible to the user - no workspace switching, no overview mode, no flicker

2. **Works across workspaces**: Can capture windows from any workspace regardless of which is currently active

3. **Always add small delay**: Add `sleep 0.1` after screenshot command before finding the file (filesystem needs time to write)

4. **Screenshot location**: Files go to `~/Pictures/Screenshots/Screenshot from YYYY-MM-DD HH-MM-SS.png`

5. **Find latest screenshot**: `ls -t ~/Pictures/Screenshots/*.png | head -1`

6. **Metadata available**: Each window has: id, title, app_id, workspace_id, is_focused, is_urgent, pid

7. **Audit logging**: All captures logged to systemd journal via `logger -t niri-capture`

8. **Clipboard behavior**: Screenshots ALWAYS copied to clipboard (niri hardcoded, cannot disable)

## Security

**READ [SECURITY.md](references/SECURITY.md) BEFORE USING THIS SKILL**

Key points:
- Captures are invisible - user won't know you're capturing other workspaces
- All captures logged to systemd journal: `journalctl --user -t niri-capture`
- Screenshots always copied to clipboard (cannot disable)
- Protect sensitive apps via niri `block-out-from "screen-capture"` rules

## Requirements

- niri compositor (verified working with niri 25.08)
- jq (for JSON parsing)
- logger (from util-linux, for audit trail)
- Configured screenshot-path in niri config (default: `~/Pictures/Screenshots/`)

## Technical Details

**How it works internally**:
- niri uses smithay's `Window` type which references Wayland surface buffers
- Applications continuously render to their surface buffers even when not visible
- `screenshot-window` action calls `mapped.render()` which renders the window buffer directly
- No compositing to output required - direct buffer-to-PNG conversion
- Result saved to file or clipboard depending on `--write-to-disk` flag

**Limitations**:
- Only works with niri compositor (uses niri-specific IPC)
- Window must exist (can't capture closed windows)
- Small delay (0.1s) needed for filesystem write

## Error Handling

- No focused window: Scripts exit with error message
- Window not found: Scripts exit with descriptive error
- Invalid window ID: niri action fails silently (check if file was created)

## Examples

See the `examples/` directory for sample usage patterns and expected outputs.
