Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions apps/desktop/src-tauri/src/deeplink_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ pub enum DeepLinkAction {
mode: RecordingMode,
},
StopRecording,
PauseRecording,
ResumeRecording,
TogglePauseRecording,
RestartRecording,
SetMicrophone {
label: Option<String>,
},
SetCamera {
id: Option<DeviceOrModelID>,
},
OpenEditor {
project_path: PathBuf,
},
Expand Down Expand Up @@ -146,6 +156,28 @@ impl DeepLinkAction {
DeepLinkAction::StopRecording => {
crate::recording::stop_recording(app.clone(), app.state()).await
}
DeepLinkAction::PauseRecording => {
crate::recording::pause_recording(app.clone(), app.state()).await
}
DeepLinkAction::ResumeRecording => {
crate::recording::resume_recording(app.clone(), app.state()).await
}
DeepLinkAction::TogglePauseRecording => {
crate::recording::toggle_pause_recording(app.clone(), app.state()).await
}
DeepLinkAction::RestartRecording => {
crate::recording::restart_recording(app.clone(), app.state())
.await
.map(|_| ())
}
DeepLinkAction::SetMicrophone { label } => {
let state = app.state::<ArcLock<App>>();
crate::set_mic_input(state, label).await
}
DeepLinkAction::SetCamera { id } => {
let state = app.state::<ArcLock<App>>();
crate::set_camera_input(app.clone(), state, id, None).await
}
Comment on lines +173 to +180
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_mic_input and set_camera_input are not pub in lib.rs, but are called here with crate:: prefix. This will fail compilation.

Suggested change
DeepLinkAction::SetMicrophone { label } => {
let state = app.state::<ArcLock<App>>();
crate::set_mic_input(state, label).await
}
DeepLinkAction::SetCamera { id } => {
let state = app.state::<ArcLock<App>>();
crate::set_camera_input(app.clone(), state, id, None).await
}
DeepLinkAction::SetMicrophone { label } => {
let state = app.state::<ArcLock<App>>();
crate::commands::set_mic_input(state, label).await
}
DeepLinkAction::SetCamera { id } => {
let state = app.state::<ArcLock<App>>();
crate::commands::set_camera_input(app.clone(), state, id, None).await
}

Or make set_mic_input and set_camera_input public in lib.rs by adding pub before async fn.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/deeplink_actions.rs
Line: 173-180

Comment:
`set_mic_input` and `set_camera_input` are not `pub` in `lib.rs`, but are called here with `crate::` prefix. This will fail compilation.

```suggestion
            DeepLinkAction::SetMicrophone { label } => {
                let state = app.state::<ArcLock<App>>();
                crate::commands::set_mic_input(state, label).await
            }
            DeepLinkAction::SetCamera { id } => {
                let state = app.state::<ArcLock<App>>();
                crate::commands::set_camera_input(app.clone(), state, id, None).await
            }
```

Or make `set_mic_input` and `set_camera_input` public in `lib.rs` by adding `pub` before `async fn`.

How can I resolve this? If you propose a fix, please make it concise.

DeepLinkAction::OpenEditor { project_path } => {
crate::open_project_from_path(Path::new(&project_path), app.clone())
}
Expand Down
4 changes: 2 additions & 2 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ impl App {
#[tauri::command]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pub(crate) should be enough here (the deeplink handler is in the same crate) and keeps these from becoming part of the public surface.

Suggested change
#[tauri::command]
pub(crate) async fn set_mic_input(state: MutableState<'_, App>, label: Option<String>) -> Result<(), String> {

#[specta::specta]
#[instrument(skip(state))]
async fn set_mic_input(state: MutableState<'_, App>, label: Option<String>) -> Result<(), String> {
pub(crate) async fn set_mic_input(state: MutableState<'_, App>, label: Option<String>) -> Result<(), String> {
let desired_label = label;

let (mic_feed, studio_handle, previous_label) = {
Expand Down Expand Up @@ -573,7 +573,7 @@ fn get_system_diagnostics() -> cap_recording::diagnostics::SystemDiagnostics {
#[specta::specta]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same idea here: crate-visible is sufficient for deeplink_actions.rs and avoids widening the API.

Suggested change
#[specta::specta]
pub(crate) async fn set_camera_input(

#[instrument(skip(app_handle, state))]
#[allow(unused_mut)]
async fn set_camera_input(
pub(crate) async fn set_camera_input(
app_handle: AppHandle,
state: MutableState<'_, App>,
id: Option<DeviceOrModelID>,
Expand Down
191 changes: 191 additions & 0 deletions extensions/raycast/DEEPLINKS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# Cap Deeplinks

Cap supports deeplinks for controlling recordings and other app functionality. This enables integration with tools like Raycast, Alfred, Shortcuts, and custom scripts.

## URL Scheme

Cap uses the `cap-desktop://` URL scheme on macOS and Windows.

## Action Format

Actions are sent as JSON in the `value` query parameter:

```
cap-desktop://action?value=<URL-encoded JSON>
```

**Important:** The JSON value MUST be URL-encoded!

## Available Actions

### Recording Controls

Unit variants are serialized as JSON strings (not objects):

#### Stop Recording
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the desktop enum keeps StopRecording/PauseRecording/etc as unit variants, these examples should be JSON strings (not { "stop_recording": {} }). Also the URL examples should encode the quotes.

Suggested change
#### Stop Recording
#### Stop Recording
```json
"stop_recording"

Pause Recording

"pause_recording"

Resume Recording

"resume_recording"

Toggle Pause/Resume

"toggle_pause_recording"

Restart Recording

Stops and immediately restarts with the same settings:

"restart_recording"

```json
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same note about suggestion formatting: avoiding backtick fences makes this apply cleanly.

Suggested change
```json
#### Stop Recording
~~~json
"stop_recording"
~~~
#### Pause Recording
~~~json
"pause_recording"
~~~
#### Resume Recording
~~~json
"resume_recording"
~~~
#### Toggle Pause/Resume
~~~json
"toggle_pause_recording"
~~~
#### Restart Recording
Stops and immediately restarts with the same settings:
~~~json
"restart_recording"
~~~

"stop_recording"
```

#### Pause Recording
```json
"pause_recording"
```

#### Resume Recording
```json
"resume_recording"
```

#### Toggle Pause/Resume
```json
"toggle_pause_recording"
```

#### Restart Recording
Stops and immediately restarts with the same settings:
```json
"restart_recording"
```

#### Start Recording
Struct variant (serialized as object):
```json
{
"start_recording": {
"capture_mode": {"screen": "Main Display"},
"camera": null,
"mic_label": null,
"capture_system_audio": false,
"mode": "instant"
}
}
```

**capture_mode options:**
- `{"screen": "Display Name"}` - Record a specific display
- `{"window": "Window Name"}` - Record a specific window

**mode options:**
- `"instant"` - Quick recording with immediate upload
- `"studio"` - Full editing capabilities

### Input Controls

#### Set Microphone
```json
{"set_microphone": {"label": "MacBook Pro Microphone"}}
```

Set to `null` to disable:
```json
{"set_microphone": {"label": null}}
```

#### Set Camera
```json
{"set_camera": {"id": "camera-device-id"}}
```

Set to `null` to disable:
```json
{"set_camera": {"id": null}}
```

### App Controls

#### Open Settings
```json
{"open_settings": {"page": null}}
```

Open a specific settings page:
```json
{"open_settings": {"page": "recordings"}}
```

#### Open Editor
```json
{"open_editor": {"project_path": "/path/to/project.cap"}}
```

## Examples

### Shell Script (macOS)

```bash
#!/bin/bash

# Stop recording (note: unit variant is a string, not object)
open "cap-desktop://action?value=%22stop_recording%22"

# Toggle pause
open "cap-desktop://action?value=%22toggle_pause_recording%22"

# Set microphone (struct variant)
open "cap-desktop://action?value=%7B%22set_microphone%22%3A%7B%22label%22%3A%22MacBook%20Pro%20Microphone%22%7D%7D"
```

### AppleScript

```applescript
tell application "System Events"
open location "cap-desktop://action?value=%22stop_recording%22"
end tell
```

### JavaScript/Node.js

```javascript
const { exec } = require('child_process');

function capAction(action) {
const json = JSON.stringify(action);
const encoded = encodeURIComponent(json);
const url = `cap-desktop://action?value=${encoded}`;
exec(`open "${url}"`);
}

// Stop recording (unit variant = string)
capAction("stop_recording");

// Toggle pause (unit variant = string)
capAction("toggle_pause_recording");

// Set microphone (struct variant = object)
capAction({ set_microphone: { label: "MacBook Pro Microphone" } });
```

### Python

```python
import subprocess
import json
import urllib.parse

def cap_action(action):
json_str = json.dumps(action)
encoded = urllib.parse.quote(json_str)
url = f"cap-desktop://action?value={encoded}"
subprocess.run(["open", url])

# Stop recording (unit variant = string)
cap_action("stop_recording")

# Toggle pause (unit variant = string)
cap_action("toggle_pause_recording")

# Set microphone (struct variant = dict)
cap_action({"set_microphone": {"label": "MacBook Pro Microphone"}})
```

## Raycast Extension

A full Raycast extension is included in `extensions/raycast/`. See its README for installation instructions.

## Troubleshooting

1. **Cap must be running** - Deeplinks only work when Cap is open
2. **URL encoding** - Make sure the JSON is properly URL-encoded
3. **Unit vs Struct variants** - Unit actions (stop, pause, etc.) are JSON strings like `"stop_recording"`, not objects like `{"stop_recording": {}}`
4. **Permissions** - Some actions require an active recording session
74 changes: 74 additions & 0 deletions extensions/raycast/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Cap Raycast Extension

Control [Cap](https://cap.so) screen recording from Raycast.

## Features

- **Start Recording** - Start a new screen recording
- **Stop Recording** - Stop the current recording
- **Pause Recording** - Pause the current recording
- **Resume Recording** - Resume a paused recording
- **Toggle Pause** - Toggle pause/resume on current recording
- **Restart Recording** - Restart the current recording
- **Open Settings** - Open Cap settings

## Requirements

- [Cap](https://cap.so) must be installed and running
- macOS and Windows supported (Cap deeplinks use the `cap-desktop://` scheme)

## How It Works

This extension uses Cap's deeplink API to control recordings. Each command sends a URL like:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs here show raw JSON embedded in the URL (and as an object). In practice value needs to be URL-encoded JSON, and unit actions likely need to be a JSON string to match the Rust enum.

Suggested change
This extension uses Cap's deeplink API to control recordings. Each command sends a URL like:
This extension uses Cap's deeplink API to control recordings. Each command sends a URL like (the `value` param must be URL-encoded JSON):

cap-desktop://action?value=%22stop_recording%22


```
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting nit: GitHub suggestions don’t handle nested triple-backtick fences well. Here’s the same change without fenced-code nesting.

Suggested change
```
This extension uses Cap's deeplink API to control recordings. Each command sends a URL like (the `value` param must be URL-encoded JSON):
cap-desktop://action?value=%22stop_recording%22

cap-desktop://action?value=%22stop_recording%22
```

Note: Unit actions (stop, pause, resume, etc.) are sent as JSON strings, while actions with parameters are sent as JSON objects.

## Installation

1. Clone this repository
2. Run `npm install` in the `extensions/raycast` directory
3. Run `npm run dev` to start development
4. Or `npm run build` to build for production

## Configuration

The extension supports the following preferences (configurable in Raycast):

- **Display Name** - Name of the display to record (leave empty for primary display)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently doesn’t actually “leave empty for primary display” (the command falls back to "Main Display"). Might be worth rewording so users don’t assume empty will always work.

Suggested change
- **Display Name** - Name of the display to record (leave empty for primary display)
- **Display Name** - Name of the display to record (defaults to "Main Display"; set this if it doesn’t match your system)

- **Recording Mode** - Choose between "instant" or "studio" mode
- **Capture System Audio** - Whether to capture system audio by default

## Available Deeplinks

| Action | Deeplink Value (URL-encoded) |
|--------|------------------------------|
| Stop Recording | `%22stop_recording%22` |
| Pause Recording | `%22pause_recording%22` |
| Resume Recording | `%22resume_recording%22` |
| Toggle Pause | `%22toggle_pause_recording%22` |
| Restart Recording | `%22restart_recording%22` |
| Set Microphone | `%7B%22set_microphone%22%3A%7B%22label%22%3A%22Microphone%20Name%22%7D%7D` |
| Set Camera | `%7B%22set_camera%22%3A%7B%22id%22%3A%22camera-id%22%7D%7D` |
| Open Settings | `%7B%22open_settings%22%3A%7B%22page%22%3Anull%7D%7D` |

### Raw JSON Values (before URL encoding)

Unit actions (no parameters):
- `"stop_recording"`
- `"pause_recording"`
- `"resume_recording"`
- `"toggle_pause_recording"`
- `"restart_recording"`

Struct actions (with parameters):
- `{"set_microphone":{"label":"Microphone Name"}}`
- `{"set_camera":{"id":"camera-id"}}`
- `{"open_settings":{"page":null}}`

## License

MIT
Binary file added extensions/raycast/assets/cap-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading