diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index fce75b4a84..449c250ef6 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -32,6 +32,15 @@ pub enum DeepLinkAction { OpenSettings { page: Option, }, + PauseRecording, + ResumeRecording, + TogglePauseRecording, + SetCamera { + device_id: Option, + }, + SetMicrophone { + label: Option, + }, } pub fn handle(app_handle: &AppHandle, urls: Vec) { @@ -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::>(); + crate::set_camera_input(app.clone(), state.clone(), device_id, None).await + } + DeepLinkAction::SetMicrophone { label } => { + let state = app.state::>(); + crate::set_mic_input(state.clone(), label).await + } } } } diff --git a/extensions/raycast/README.md b/extensions/raycast/README.md new file mode 100644 index 0000000000..7be07e3315 --- /dev/null +++ b/extensions/raycast/README.md @@ -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` | +| 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 diff --git a/extensions/raycast/assets/cap-icon.png b/extensions/raycast/assets/cap-icon.png new file mode 100644 index 0000000000..f19a241416 Binary files /dev/null and b/extensions/raycast/assets/cap-icon.png differ diff --git a/extensions/raycast/package.json b/extensions/raycast/package.json new file mode 100644 index 0000000000..5694cf44a8 --- /dev/null +++ b/extensions/raycast/package.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://www.raycast.com/schemas/extension.json", + "name": "cap", + "title": "Cap", + "description": "Control Cap screen recorder with Raycast", + "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", + "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" + } +} diff --git a/extensions/raycast/src/pause-recording.tsx b/extensions/raycast/src/pause-recording.tsx new file mode 100644 index 0000000000..7a1182799b --- /dev/null +++ b/extensions/raycast/src/pause-recording.tsx @@ -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"); + } +} diff --git a/extensions/raycast/src/resume-recording.tsx b/extensions/raycast/src/resume-recording.tsx new file mode 100644 index 0000000000..bfba0349a6 --- /dev/null +++ b/extensions/raycast/src/resume-recording.tsx @@ -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"); + } +} diff --git a/extensions/raycast/src/start-recording.tsx b/extensions/raycast/src/start-recording.tsx new file mode 100644 index 0000000000..bfd7971dcb --- /dev/null +++ b/extensions/raycast/src/start-recording.tsx @@ -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"); + } +} diff --git a/extensions/raycast/src/stop-recording.tsx b/extensions/raycast/src/stop-recording.tsx new file mode 100644 index 0000000000..a94ea60c77 --- /dev/null +++ b/extensions/raycast/src/stop-recording.tsx @@ -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"); + } +} diff --git a/extensions/raycast/src/toggle-pause.tsx b/extensions/raycast/src/toggle-pause.tsx new file mode 100644 index 0000000000..a16854bbde --- /dev/null +++ b/extensions/raycast/src/toggle-pause.tsx @@ -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"); + } +}