diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index a117028487..cf176d81b0 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -13,6 +13,7 @@ use crate::{App, ArcLock, recording::StartRecordingInputs, windows::ShowCapWindo pub enum CaptureMode { Screen(String), Window(String), + PrimaryScreen, } #[derive(Debug, Serialize, Deserialize)] @@ -26,6 +27,14 @@ pub enum DeepLinkAction { mode: RecordingMode, }, StopRecording, + PauseRecording, + ResumeRecording, + SwitchMicrophone { + mic_label: Option, + }, + SwitchCamera { + camera: Option, + }, OpenEditor { project_path: PathBuf, }, @@ -121,6 +130,11 @@ impl DeepLinkAction { crate::set_mic_input(state.clone(), mic_label).await?; let capture_target: ScreenCaptureTarget = match capture_mode { + CaptureMode::PrimaryScreen => cap_recording::screen_capture::list_displays() + .into_iter() + .next() + .map(|(s, _)| ScreenCaptureTarget::Display { id: s.id }) + .ok_or("No screens available".to_string())?, CaptureMode::Screen(name) => cap_recording::screen_capture::list_displays() .into_iter() .find(|(s, _)| s.name == name) @@ -147,6 +161,18 @@ 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::SwitchMicrophone { mic_label } => { + crate::set_mic_input(app.state(), mic_label).await + } + DeepLinkAction::SwitchCamera { camera } => { + crate::set_camera_input(app.clone(), app.state(), camera, None).await + } DeepLinkAction::OpenEditor { project_path } => { crate::open_project_from_path(Path::new(&project_path), app.clone()) } diff --git a/apps/raycast/package.json b/apps/raycast/package.json new file mode 100644 index 0000000000..24b8f8c279 --- /dev/null +++ b/apps/raycast/package.json @@ -0,0 +1,79 @@ +{ + "$schema": "https://www.raycast.com/downloads/extension.schema.json", + "name": "cap-controls", + "title": "Cap Controls", + "description": "Control the Cap screen recording app directly from Raycast", + "icon": "command-icon.png", + "author": "Ojas2095", + "license": "MIT", + "commands": [ + { + "name": "start", + "title": "Start Recording", + "description": "Start a Cap recording", + "mode": "no-view" + }, + { + "name": "stop", + "title": "Stop Recording", + "description": "Stops the current Cap recording", + "mode": "no-view" + }, + { + "name": "pause", + "title": "Pause Recording", + "description": "Pauses the current Cap recording", + "mode": "no-view" + }, + { + "name": "resume", + "title": "Resume Recording", + "description": "Resumes the paused Cap recording", + "mode": "no-view" + }, + { + "name": "switch-mic", + "title": "Switch Microphone", + "description": "Switch Cap's recording microphone", + "mode": "no-view", + "arguments": [ + { + "name": "micLabel", + "type": "text", + "placeholder": "Microphone Name (or 'none')", + "required": true + } + ] + }, + { + "name": "switch-camera", + "title": "Switch Camera", + "description": "Switch Cap's recording camera", + "mode": "no-view", + "arguments": [ + { + "name": "cameraId", + "type": "text", + "placeholder": "Camera ID (or 'none')", + "required": true + } + ] + } + ], + "dependencies": { + "@raycast/api": "^1.65.0" + }, + "devDependencies": { + "@raycast/eslint-config": "^1.0.6", + "@types/node": "20.8.10", + "@types/react": "18.2.27", + "eslint": "^8.51.0", + "prettier": "^3.0.3", + "typescript": "^5.2.2" + }, + "scripts": { + "build": "ray build -e dist", + "dev": "ray develop", + "publish": "npx @raycast/api@latest publish" + } +} diff --git a/apps/raycast/src/pause.ts b/apps/raycast/src/pause.ts new file mode 100644 index 0000000000..56397c5baa --- /dev/null +++ b/apps/raycast/src/pause.ts @@ -0,0 +1,5 @@ +import { sendCapCommand } from "./utils"; + +export default async function Command() { + await sendCapCommand("pause_recording"); +} diff --git a/apps/raycast/src/resume.ts b/apps/raycast/src/resume.ts new file mode 100644 index 0000000000..0fd6f5e62a --- /dev/null +++ b/apps/raycast/src/resume.ts @@ -0,0 +1,5 @@ +import { sendCapCommand } from "./utils"; + +export default async function Command() { + await sendCapCommand("resume_recording"); +} diff --git a/apps/raycast/src/start.ts b/apps/raycast/src/start.ts new file mode 100644 index 0000000000..c61d09d6f5 --- /dev/null +++ b/apps/raycast/src/start.ts @@ -0,0 +1,11 @@ +import { sendCapCommand } from "./utils"; + +export default async function Command() { + await sendCapCommand("start_recording", { + capture_mode: "primary_screen", + camera: null, + mic_label: null, + capture_system_audio: true, + mode: "Screen" + }); +} diff --git a/apps/raycast/src/stop.ts b/apps/raycast/src/stop.ts new file mode 100644 index 0000000000..782f3e1ccc --- /dev/null +++ b/apps/raycast/src/stop.ts @@ -0,0 +1,5 @@ +import { sendCapCommand } from "./utils"; + +export default async function Command() { + await sendCapCommand("stop_recording"); +} diff --git a/apps/raycast/src/switch-camera.ts b/apps/raycast/src/switch-camera.ts new file mode 100644 index 0000000000..37dc4e18ab --- /dev/null +++ b/apps/raycast/src/switch-camera.ts @@ -0,0 +1,8 @@ +import { LaunchProps } from "@raycast/api"; +import { sendCapCommand } from "./utils"; + +export default async function Command(props: LaunchProps<{ arguments: { cameraId: string } }>) { + await sendCapCommand("switch_camera", { + camera: props.arguments.cameraId === "none" ? null : { DeviceID: props.arguments.cameraId } + }); +} diff --git a/apps/raycast/src/switch-mic.ts b/apps/raycast/src/switch-mic.ts new file mode 100644 index 0000000000..d47117f553 --- /dev/null +++ b/apps/raycast/src/switch-mic.ts @@ -0,0 +1,8 @@ +import { LaunchProps } from "@raycast/api"; +import { sendCapCommand } from "./utils"; + +export default async function Command(props: LaunchProps<{ arguments: { micLabel: string } }>) { + await sendCapCommand("switch_microphone", { + mic_label: props.arguments.micLabel === "none" ? null : props.arguments.micLabel + }); +} diff --git a/apps/raycast/src/utils.ts b/apps/raycast/src/utils.ts new file mode 100644 index 0000000000..de2113b35a --- /dev/null +++ b/apps/raycast/src/utils.ts @@ -0,0 +1,17 @@ +import { open, showHUD } from "@raycast/api"; + +export async function sendCapCommand(action: string, objectValue?: Record) { + + const valuePayload = objectValue + ? JSON.stringify({ [action]: objectValue }) + : `"${action}"`; + + const url = `cap://action?value=${encodeURIComponent(valuePayload)}`; + try { + await open(url); + await showHUD(`Sent to Cap`); + } catch (error) { + await showHUD("Failed to communicate with Cap. Is it installed?"); + console.error(error); + } +} diff --git a/apps/raycast/tsconfig.json b/apps/raycast/tsconfig.json new file mode 100644 index 0000000000..440256b574 --- /dev/null +++ b/apps/raycast/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "target": "ES2021", + "jsx": "react", + "lib": ["ES2021", "DOM"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "node" + }, + "include": ["src/**/*"] +}