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
34 changes: 34 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,18 @@ pub enum DeepLinkAction {
mode: RecordingMode,
},
StopRecording,
PauseRecording,
ResumeRecording,
TogglePauseRecording,
TakeScreenshot {
target: ScreenCaptureTarget,
},
SetCamera {
Copy link

Choose a reason for hiding this comment

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

Small thing: set_camera_input/set_mic_input already accept Option<...> so callers can clear the selection; making the deeplink payload accept null here would keep things consistent with StartRecording and avoid needing separate actions.

Suggested change
SetCamera {
SetCamera {
camera_id: Option<DeviceOrModelID>,
},
SetMicrophone {
mic_label: Option<String>,
},

camera_id: Option<DeviceOrModelID>,
},
SetMicrophone {
mic_label: Option<String>,
},
OpenEditor {
project_path: PathBuf,
},
Expand Down Expand Up @@ -146,6 +158,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::TakeScreenshot { target } => {
crate::recording::take_screenshot(app.clone(), target)
.await
.map(|_| ())
}
DeepLinkAction::SetCamera { camera_id } => {
Copy link

Choose a reason for hiding this comment

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

Follow-up for the Option change above so null just passes through to the underlying setters.

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

let state = app.state::<ArcLock<App>>();
crate::set_camera_input(app.clone(), state, camera_id).await
}
DeepLinkAction::SetMicrophone { mic_label } => {
let state = app.state::<ArcLock<App>>();
crate::set_mic_input(state, mic_label).await
}
DeepLinkAction::OpenEditor { project_path } => {
crate::open_project_from_path(Path::new(&project_path), app.clone())
}
Expand Down
33 changes: 33 additions & 0 deletions raycast-cap-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Cap Raycast Extension

Control Cap screen recording directly from Raycast.

## Features

- **Start Recording**: Start a new Cap recording
- **Stop Recording**: Stop the current recording
- **Pause/Resume**: Pause and resume recordings
- **Toggle Pause**: Toggle pause state with one command
- **Take Screenshot**: Capture screenshots with Cap

## Installation

1. Make sure you have [Cap](https://cap.so) installed
2. Install this extension in Raycast
3. Use the commands to control Cap

## Usage

All commands use Cap's deeplink protocol to communicate with the desktop app. Make sure Cap is running before using these commands.

## Development

```bash
npm install
npm run dev
```

## License

MIT

Binary file added raycast-cap-extension/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 70 additions & 0 deletions raycast-cap-extension/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"$schema": "https://www.raycast.com/schemas/extension.json",
"name": "cap",
"title": "Cap",
"description": "Control Cap screen recording from Raycast",
"icon": "icon.png",
Copy link

Choose a reason for hiding this comment

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

icon points at icon.png, but the PR doesn"t add that file. Might be worth either adding the icon asset or pointing at an existing one so ray build/publish doesn"t fail.

Copy link
Contributor

Choose a reason for hiding this comment

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

logic: missing icon.png file - extension will fail to load in Raycast without this required asset

Prompt To Fix With AI
This is a comment left during a code review.
Path: raycast-cap-extension/package.json
Line: 6:6

Comment:
**logic:** missing `icon.png` file - extension will fail to load in Raycast without this required asset

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

"author": "bikramadhikari001",
"categories": [
"Productivity",
"Media"
],
"license": "MIT",
"commands": [
{
"name": "start-recording",
"title": "Start Recording",
"description": "Start a new Cap recording",
"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",
"description": "Resume the paused recording",
"mode": "no-view"
},
{
"name": "toggle-pause-recording",
"title": "Toggle Pause Recording",
"description": "Toggle pause/resume for the current recording",
"mode": "no-view"
},
{
"name": "take-screenshot",
"title": "Take Screenshot",
"description": "Take a screenshot with Cap",
"mode": "no-view"
}
],
"dependencies": {
"@raycast/api": "^1.87.0",
"@raycast/utils": "^1.19.0"
},
"devDependencies": {
"@raycast/eslint-config": "^1.0.11",
"@types/node": "20.8.10",
"@types/react": "18.3.3",
"eslint": "^8.57.0",
"prettier": "^3.2.5",
"typescript": "^5.4.5"
},
"scripts": {
"build": "ray build -e dist",
"dev": "ray develop",
"fix-lint": "ray lint --fix",
"lint": "ray lint",
"publish": "npx @raycast/api@latest publish"
}
}
13 changes: 13 additions & 0 deletions raycast-cap-extension/src/pause-recording.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { open, showToast, Toast } from "@raycast/api";

export default async function Command() {
try {
const action = { pause_recording: {} };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
await showToast({ style: Toast.Style.Success, title: "Pause recording requested" });
} catch (error) {
await showToast({ style: Toast.Style.Failure, title: "Failed to pause recording", message: String(error) });
}
}

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

export default async function Command() {
try {
const action = { resume_recording: {} };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
await showToast({ style: Toast.Style.Success, title: "Resume recording requested" });
} catch (error) {
await showToast({ style: Toast.Style.Failure, title: "Failed to resume recording", message: String(error) });
}
}

21 changes: 21 additions & 0 deletions raycast-cap-extension/src/start-recording.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { open, showToast, Toast } from "@raycast/api";

export default async function Command() {
try {
const action = {
start_recording: {
capture_mode: { screen: "" },
Copy link

Choose a reason for hiding this comment

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

capture_mode.screen is empty here, so StartRecording will almost certainly fail with No screen with name "". Might be worth surfacing this as a Raycast preference (or prompt) so users can provide their display/window name.

Copy link

Choose a reason for hiding this comment

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

Using capture_mode: { screen: "" } will always trigger a "No screen with name """ error in the desktop app (it looks up displays by name). Probably worth making the screen/window name a Raycast preference (or teaching the backend to treat empty as "primary display").

camera: null,
mic_label: null,
capture_system_audio: true,
mode: "studio"
}
};
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
await showToast({ style: Toast.Style.Success, title: "Start recording requested" });
} catch (error) {
await showToast({ style: Toast.Style.Failure, title: "Failed to start recording", message: String(error) });
}
}

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

export default async function Command() {
try {
const action = { stop_recording: {} };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
await showToast({ style: Toast.Style.Success, title: "Stop recording requested" });
} catch (error) {
await showToast({ style: Toast.Style.Failure, title: "Failed to stop recording", message: String(error) });
}
}

17 changes: 17 additions & 0 deletions raycast-cap-extension/src/take-screenshot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { open, showToast, Toast } from "@raycast/api";

export default async function Command() {
try {
const action = {
take_screenshot: {
target: { variant: "display", id: "0" }
}
};
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
await showToast({ style: Toast.Style.Success, title: "Screenshot requested" });
} catch (error) {
await showToast({ style: Toast.Style.Failure, title: "Failed to take screenshot", message: String(error) });
}
}

13 changes: 13 additions & 0 deletions raycast-cap-extension/src/toggle-pause-recording.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { open, showToast, Toast } from "@raycast/api";

export default async function Command() {
try {
const action = { toggle_pause_recording: {} };
const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`;
await open(url);
await showToast({ style: Toast.Style.Success, title: "Toggle pause requested" });
} catch (error) {
await showToast({ style: Toast.Style.Failure, title: "Failed to toggle pause", message: String(error) });
}
}

17 changes: 17 additions & 0 deletions raycast-cap-extension/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "commonjs",
"jsx": "react",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"moduleResolution": "node"
},
"include": ["src/**/*"]
}