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
26 changes: 26 additions & 0 deletions apps/desktop/src-tauri/src/deeplink_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ pub enum DeepLinkAction {
OpenSettings {
page: Option<String>,
},
PauseRecording,
ResumeRecording,
TogglePauseRecording,
SetCamera {
device_id: Option<DeviceOrModelID>,
},
SetMicrophone {
label: Option<String>,
},
}

pub fn handle(app_handle: &AppHandle, urls: Vec<Url>) {
Expand Down Expand Up @@ -152,6 +161,23 @@ impl DeepLinkAction {
DeepLinkAction::OpenSettings { page } => {
crate::show_window(app.clone(), ShowCapWindow::Settings { page }).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::SetCamera { device_id } => {
let state = app.state::<ArcLock<App>>();
crate::set_camera_input(app.clone(), state.clone(), device_id, None).await
}
DeepLinkAction::SetMicrophone { label } => {
let state = app.state::<ArcLock<App>>();
crate::set_mic_input(state.clone(), label).await
}
Comment on lines +173 to +180
Copy link

Choose a reason for hiding this comment

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

With the new deeplink actions, an empty payload like {"set_camera":{}} parses as None and gets forwarded to the setters. If None isn't meant to clear the selection, it may be worth rejecting missing params explicitly.

Suggested change
DeepLinkAction::SetCamera { device_id } => {
let state = app.state::<ArcLock<App>>();
crate::set_camera_input(app.clone(), state.clone(), device_id, None).await
}
DeepLinkAction::SetMicrophone { label } => {
let state = app.state::<ArcLock<App>>();
crate::set_mic_input(state.clone(), label).await
}
DeepLinkAction::SetCamera { device_id } => {
let device_id = device_id.ok_or_else(|| "device_id is required".to_string())?;
let state = app.state::<ArcLock<App>>();
crate::set_camera_input(app.clone(), state.clone(), Some(device_id), None).await
}
DeepLinkAction::SetMicrophone { label } => {
let label = label.ok_or_else(|| "label is required".to_string())?;
let state = app.state::<ArcLock<App>>();
crate::set_mic_input(state.clone(), Some(label)).await
}

}
}
}
59 changes: 59 additions & 0 deletions extensions/raycast/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Cap Raycast Extension

Control Cap screen recorder directly from Raycast.

## Features

- **Start Recording** - Begin a new screen recording
- **Stop Recording** - End the current recording
- **Pause Recording** - Pause the current recording
- **Resume Recording** - Resume a paused recording
- **Toggle Pause** - Toggle between paused/recording states

## Installation

1. Install the Cap desktop app from [cap.so](https://cap.so)
2. Open Raycast
3. Search for "Cap" in the Raycast Store
4. Install the extension

## Development

```bash
cd extensions/raycast
npm install
npm run dev
```

## How it Works

This extension uses Cap's deeplink protocol (`cap-desktop://`) to communicate with the desktop app. Each command sends a specific deeplink action:

| Command | Deeplink Action |
|---------|-----------------|
| Start Recording | `start_recording` |
Copy link

Choose a reason for hiding this comment

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

Docs mismatch: Start Recording here says start_recording, but the implementation currently sends open_settings (recording page). Either make the command actually send start_recording, or update the docs/command naming to match.

Suggested change
| Start Recording | `start_recording` |
| Start Recording | `open_settings` |

| Stop Recording | `stop_recording` |
| Pause Recording | `pause_recording` |
| Resume Recording | `resume_recording` |
| Toggle Pause | `toggle_pause_recording` |

## Deeplink Format

```
cap-desktop://action?value={"action_name": {...params}}
```

Example:
```
cap-desktop://action?value={"pause_recording":null}
```

## Requirements

- macOS 11.0 or later
- Cap desktop app installed
- Raycast installed

## 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.
61 changes: 61 additions & 0 deletions extensions/raycast/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"$schema": "https://www.raycast.com/schemas/extension.json",
"name": "cap",
"title": "Cap",
"description": "Control Cap screen recorder with Raycast",
Copy link
Contributor

Choose a reason for hiding this comment

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

The referenced icon file cap-icon.png is missing from the extensions/raycast/ directory. Ensure the icon file is added before publishing.

Suggested change
"description": "Control Cap screen recorder with Raycast",
"icon": "cap-icon.png",
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/raycast/package.json
Line: 5:5

Comment:
The referenced icon file `cap-icon.png` is missing from the `extensions/raycast/` directory. Ensure the icon file is added before publishing.

```suggestion
  "icon": "cap-icon.png",
```

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

"icon": "assets/cap-icon.png",
"author": "cap-software",
"categories": ["Productivity", "Media"],
"license": "MIT",
"commands": [
{
"name": "start-recording",
"title": "Start Recording",
"description": "Start a screen recording with Cap",
"mode": "no-view"
},
{
"name": "stop-recording",
"title": "Stop Recording",
"description": "Stop the current recording",
"mode": "no-view"
},
{
"name": "pause-recording",
"title": "Pause Recording",
"description": "Pause the current recording",
"mode": "no-view"
},
{
"name": "resume-recording",
"title": "Resume Recording",
Copy link

Choose a reason for hiding this comment

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

Small formatting nit: stray trailing whitespace.

Suggested change
"title": "Resume Recording",
"title": "Resume Recording",

"description": "Resume a paused recording",
"mode": "no-view"
},
{
"name": "toggle-pause",
"title": "Toggle Pause",
"description": "Toggle pause/resume on current recording",
"mode": "no-view"
}
],
"dependencies": {
"@raycast/api": "^1.83.0"
},
"devDependencies": {
"@raycast/eslint-config": "^1.0.11",
"@types/node": "20.8.10",
"@types/react": "18.3.3",
"eslint": "^8.57.0",
"prettier": "^3.3.3",
"typescript": "^5.4.5"
},
"scripts": {
"build": "ray build --skip-types -e dist -o dist",
"dev": "ray develop",
"fix-lint": "ray lint --fix",
"lint": "ray lint",
"prepublishOnly": "echo \"\\n\\nIt seems like you are trying to publish the Raycast extension to npm.\\n\\nIf you did intend to publish it to npm, remove the \\`prepublishOnly\\` script and rerun \\`npm publish\\` again.\\nIf you wanted to publish it to the Raycast Store instead, use \\`npm run publish\\` instead.\\n\\n\" && exit 1",
"publish": "npx @raycast/api@latest publish"
}
}
13 changes: 13 additions & 0 deletions extensions/raycast/src/pause-recording.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { open, showHUD } from "@raycast/api";

export default async function Command() {
const action = { pause_recording: null };
const deeplink = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;

try {
await open(deeplink);
await showHUD("⏸️ Recording paused");
} catch (error) {
await showHUD("❌ Failed to pause recording");
}
}
Comment on lines +1 to +13
Copy link

Choose a reason for hiding this comment

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

Minor TS/ESLint cleanup: the caught error isn't used, so catch {} avoids an unused var. Same pattern applies to the other no-view commands.

Suggested change
import { open, showHUD } from "@raycast/api";
export default async function Command() {
const action = { pause_recording: null };
const deeplink = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(deeplink);
await showHUD("⏸️ Recording paused");
} catch (error) {
await showHUD("Failed to pause recording");
}
}
import { open, showHUD } from "@raycast/api";
export default async function Command() {
const action = { pause_recording: null };
const deeplink = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
try {
await open(deeplink);
await showHUD("Recording paused");
} catch {
await showHUD("Failed to pause recording");
}
}

13 changes: 13 additions & 0 deletions extensions/raycast/src/resume-recording.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { open, showHUD } from "@raycast/api";

export default async function Command() {
const action = { resume_recording: null };
const deeplink = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;

try {
await open(deeplink);
await showHUD("▶️ Recording resumed");
} catch (error) {
await showHUD("❌ Failed to resume recording");
}
}
26 changes: 26 additions & 0 deletions extensions/raycast/src/start-recording.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { open, showHUD, getApplications } from "@raycast/api";

export default async function Command() {
const apps = await getApplications();
const capInstalled = apps.some(
(app) => app.bundleId === "so.cap.desktop" || app.bundleId === "so.cap.desktop.dev"
);

if (!capInstalled) {
await showHUD("❌ Cap is not installed");
return;
}

const action = {
open_settings: { page: "recording" }
};

const deeplink = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;

try {
await open(deeplink);
await showHUD("📺 Opening Cap recording settings...");
} catch (error) {
await showHUD("❌ Failed to open Cap");
}
}
13 changes: 13 additions & 0 deletions extensions/raycast/src/stop-recording.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { open, showHUD } from "@raycast/api";

export default async function Command() {
const action = { stop_recording: null };
const deeplink = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;

try {
await open(deeplink);
await showHUD("⏹️ Recording stopped");
} catch (error) {
await showHUD("❌ Failed to stop recording");
}
}
13 changes: 13 additions & 0 deletions extensions/raycast/src/toggle-pause.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { open, showHUD } from "@raycast/api";

export default async function Command() {
const action = { toggle_pause_recording: null };
const deeplink = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;

try {
await open(deeplink);
await showHUD("⏯️ Recording pause toggled");
} catch (error) {
await showHUD("❌ Failed to toggle pause");
}
}