From cbde5b4f6dc3b1eb7d60d5ecc0a3827ae2fac465 Mon Sep 17 00:00:00 2001 From: Preet Rank Date: Fri, 23 Jan 2026 22:03:47 +0530 Subject: [PATCH 1/2] feat: add deeplink support for recording controls and Raycast extension (#1540) --- .../desktop/src-tauri/src/deeplink_actions.rs | 83 ++++++++ apps/raycast-extension/README.md | 177 ++++++++++++++++++ apps/raycast-extension/package.json | 78 ++++++++ .../raycast-extension/src/pause-recording.tsx | 19 ++ .../src/resume-recording.tsx | 19 ++ .../raycast-extension/src/start-recording.tsx | 86 +++++++++ apps/raycast-extension/src/stop-recording.tsx | 19 ++ apps/raycast-extension/src/switch-camera.tsx | 57 ++++++ .../src/switch-microphone.tsx | 57 ++++++ .../raycast-extension/src/take-screenshot.tsx | 63 +++++++ apps/raycast-extension/src/toggle-pause.tsx | 19 ++ apps/raycast-extension/src/utils/deeplink.ts | 135 +++++++++++++ apps/raycast-extension/tsconfig.json | 22 +++ 13 files changed, 834 insertions(+) create mode 100644 apps/raycast-extension/README.md create mode 100644 apps/raycast-extension/package.json create mode 100644 apps/raycast-extension/src/pause-recording.tsx create mode 100644 apps/raycast-extension/src/resume-recording.tsx create mode 100644 apps/raycast-extension/src/start-recording.tsx create mode 100644 apps/raycast-extension/src/stop-recording.tsx create mode 100644 apps/raycast-extension/src/switch-camera.tsx create mode 100644 apps/raycast-extension/src/switch-microphone.tsx create mode 100644 apps/raycast-extension/src/take-screenshot.tsx create mode 100644 apps/raycast-extension/src/toggle-pause.tsx create mode 100644 apps/raycast-extension/src/utils/deeplink.ts create mode 100644 apps/raycast-extension/tsconfig.json diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index dbd90f667f..fab4a5ee7e 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -26,6 +26,22 @@ pub enum DeepLinkAction { mode: RecordingMode, }, StopRecording, + PauseRecording, + ResumeRecording, + TogglePauseRecording, + TakeScreenshot { + capture_target: ScreenCaptureTarget, + }, + SetCamera { + id: Option, + }, + SetMicrophone { + label: Option, + }, + ListCameras, + ListMicrophones, + ListDisplays, + ListWindows, OpenEditor { project_path: PathBuf, }, @@ -146,6 +162,73 @@ 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 { capture_target } => { + crate::recording::take_screenshot(app.clone(), capture_target) + .await + .map(|_| ()) + } + DeepLinkAction::SetCamera { id } => { + crate::set_camera_input(app.clone(), app.state(), id).await + } + DeepLinkAction::SetMicrophone { label } => { + crate::set_mic_input(app.state(), label).await + } + DeepLinkAction::ListCameras => { + let cameras = crate::recording::list_cameras(); + let cameras_json = serde_json::to_string(&cameras) + .map_err(|e| format!("Failed to serialize cameras: {}", e))?; + eprintln!("Available cameras: {}", cameras_json); + Ok(()) + } + DeepLinkAction::ListMicrophones => { + use cap_recording::feeds::microphone::MicrophoneFeed; + let microphones: Vec = MicrophoneFeed::list().keys().cloned().collect(); + let mics_json = serde_json::to_string(µphones) + .map_err(|e| format!("Failed to serialize microphones: {}", e))?; + eprintln!("Available microphones: {}", mics_json); + Ok(()) + } + DeepLinkAction::ListDisplays => { + let displays = cap_recording::screen_capture::list_displays(); + let displays_data: Vec<_> = displays + .into_iter() + .map(|(capture_display, _)| { + serde_json::json!({ + "id": capture_display.id, + "name": capture_display.name, + }) + }) + .collect(); + let displays_json = serde_json::to_string(&displays_data) + .map_err(|e| format!("Failed to serialize displays: {}", e))?; + eprintln!("Available displays: {}", displays_json); + Ok(()) + } + DeepLinkAction::ListWindows => { + let windows = cap_recording::screen_capture::list_windows(); + let windows_data: Vec<_> = windows + .into_iter() + .map(|(capture_window, _)| { + serde_json::json!({ + "id": capture_window.id, + "name": capture_window.name, + }) + }) + .collect(); + let windows_json = serde_json::to_string(&windows_data) + .map_err(|e| format!("Failed to serialize windows: {}", e))?; + eprintln!("Available windows: {}", windows_json); + Ok(()) + } DeepLinkAction::OpenEditor { project_path } => { crate::open_project_from_path(Path::new(&project_path), app.clone()) } diff --git a/apps/raycast-extension/README.md b/apps/raycast-extension/README.md new file mode 100644 index 0000000000..76d51b5fb8 --- /dev/null +++ b/apps/raycast-extension/README.md @@ -0,0 +1,177 @@ +# Cap Raycast Extension + +Control [Cap](https://cap.so) screen recording directly from Raycast! + +## Features + +This extension provides quick access to Cap's recording functionality through Raycast commands: + +### Recording Controls +- **Start Recording** - Start a new screen or window recording with customizable options +- **Stop Recording** - Stop the current recording +- **Pause Recording** - Pause the active recording +- **Resume Recording** - Resume a paused recording +- **Toggle Pause** - Toggle between paused and active recording states + +### Capture +- **Take Screenshot** - Capture a screenshot of a specific display or window + +### Hardware Management +- **Switch Camera** - Change the active camera input or disable camera +- **Switch Microphone** - Change the active microphone input or mute + +## Requirements + +- [Cap](https://cap.so) desktop application (v0.3.0 or later) must be installed +- macOS (Raycast is macOS-only) +- Cap must be running to respond to commands + +## Installation + +### From Raycast Store (Coming Soon) +1. Open Raycast +2. Search for "Cap" +3. Click "Install Extension" + +### Manual Installation (Development) +1. Clone the repository: + ```bash + git clone https://github.com/CapSoftware/Cap.git + cd Cap/apps/raycast-extension + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +3. Build the extension: + ```bash + npm run dev + ``` + +4. The extension will automatically be available in Raycast during development + +## Usage + +### Starting a Recording +1. Open Raycast (⌘ + Space) +2. Type "Start Recording" +3. Fill in the form: + - **Capture Type**: Choose Screen or Window + - **Target Name**: Enter the display/window name + - **Recording Mode**: Studio (editable) or Instant (immediately uploaded) + - **Enable Camera**: Toggle camera on/off + - **Enable Microphone**: Toggle microphone on/off + - **Capture System Audio**: Include system audio in recording +4. Press Enter to start recording + +### Finding Display/Window Names +Use the built-in Cap commands to list available targets: +- In your terminal, run Cap with `--list-displays` or `--list-windows` flags +- Or check the Cap UI for display/window names + +### Quick Actions +All other commands are instant actions: +- **Stop Recording**: Simply run the command +- **Pause/Resume**: Run the respective command while recording +- **Toggle Pause**: Quick shortcut to toggle pause state +- **Take Screenshot**: Fill in the target and capture instantly + +### Hardware Switching +1. Run "Switch Camera" or "Switch Microphone" +2. Enter the device ID or name +3. Toggle enable/disable as needed +4. Press Enter to switch + +**Tip**: Use the "List Cameras" and "List Microphones" Cap commands to see available devices + +## Commands Reference + +| Command | Shortcut | Description | +|---------|----------|-------------| +| Start Recording | - | Start a new recording with options | +| Stop Recording | - | Stop the current recording | +| Pause Recording | - | Pause the active recording | +| Resume Recording | - | Resume a paused recording | +| Toggle Pause | - | Toggle pause state | +| Take Screenshot | - | Capture a screenshot | +| Switch Camera | - | Change camera input | +| Switch Microphone | - | Change microphone input | + +## Troubleshooting + +### Command Not Working +- Ensure Cap is running +- Check that Cap has necessary permissions (Screen Recording, Camera, Microphone) +- Verify you're running Cap v0.3.0 or later with deeplink support + +### "Failed to Start Recording" +- Double-check the display/window name is correct +- Ensure the target display/window exists and is accessible +- Check Cap's permissions in System Settings > Privacy & Security + +### Camera/Microphone Not Switching +- Verify the device ID/name is correct +- Check that the device is connected and recognized by your system +- Ensure Cap has permission to access camera/microphone + +## Development + +### Project Structure +``` +src/ +├── utils/ +│ └── deeplink.ts # Deeplink utility functions +├── start-recording.tsx # Start recording command +├── stop-recording.tsx # Stop recording command +├── pause-recording.tsx # Pause command +├── resume-recording.tsx # Resume command +├── toggle-pause.tsx # Toggle pause command +├── take-screenshot.tsx # Screenshot command +├── switch-camera.tsx # Camera switching command +└── switch-microphone.tsx # Microphone switching command +``` + +### Building +```bash +npm run build +``` + +### Linting +```bash +npm run lint +npm run fix-lint +``` + +## How It Works + +This extension communicates with Cap using the `cap-desktop://` URL scheme. Each command constructs a deeplink URL with JSON-encoded actions and opens it, which Cap intercepts and executes. + +Example deeplink: +``` +cap-desktop://action?value={"pauseRecording":{}} +``` + +## Contributing + +Contributions are welcome! Please: +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Submit a pull request + +## License + +MIT License - see LICENSE file for details + +## Links + +- [Cap Website](https://cap.so) +- [Cap GitHub](https://github.com/CapSoftware/Cap) +- [Report Issues](https://github.com/CapSoftware/Cap/issues) +- [Raycast](https://raycast.com) + +## Credits + +Created for the Cap deeplinks bounty (#1540) diff --git a/apps/raycast-extension/package.json b/apps/raycast-extension/package.json new file mode 100644 index 0000000000..604837056d --- /dev/null +++ b/apps/raycast-extension/package.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://www.raycast.com/schemas/extension.json", + "name": "cap", + "title": "Cap", + "description": "Control Cap screen recording from Raycast", + "icon": "icon.png", + "author": "cap", + "license": "MIT", + "commands": [ + { + "name": "start-recording", + "title": "Start Recording", + "description": "Start a new screen recording", + "mode": "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", + "title": "Toggle Pause", + "description": "Toggle recording pause state", + "mode": "no-view" + }, + { + "name": "take-screenshot", + "title": "Take Screenshot", + "description": "Capture a screenshot", + "mode": "view" + }, + { + "name": "switch-camera", + "title": "Switch Camera", + "description": "Change camera input", + "mode": "view" + }, + { + "name": "switch-microphone", + "title": "Switch Microphone", + "description": "Change microphone input", + "mode": "view" + } + ], + "dependencies": { + "@raycast/api": "^1.83.2", + "@raycast/utils": "^1.19.0" + }, + "devDependencies": { + "@raycast/eslint-config": "^1.0.11", + "@types/node": "^20.16.5", + "@types/react": "^18.3.3", + "eslint": "^8.57.0", + "prettier": "^3.3.3", + "typescript": "^5.5.4" + }, + "scripts": { + "build": "ray build -e dist", + "dev": "ray develop", + "fix-lint": "ray lint --fix", + "lint": "ray lint", + "publish": "npx @raycast/api@latest publish" + } +} \ No newline at end of file diff --git a/apps/raycast-extension/src/pause-recording.tsx b/apps/raycast-extension/src/pause-recording.tsx new file mode 100644 index 0000000000..78ebe24f4b --- /dev/null +++ b/apps/raycast-extension/src/pause-recording.tsx @@ -0,0 +1,19 @@ +import { showToast, Toast } from "@raycast/api"; +import * as deeplink from "./utils/deeplink"; + +export default async function Command() { + try { + await deeplink.pauseRecording(); + await showToast({ + style: Toast.Style.Success, + title: "Recording Paused", + message: "Cap recording has been paused", + }); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to Pause Recording", + message: error instanceof Error ? error.message : "Unknown error occurred", + }); + } +} diff --git a/apps/raycast-extension/src/resume-recording.tsx b/apps/raycast-extension/src/resume-recording.tsx new file mode 100644 index 0000000000..e1846b517c --- /dev/null +++ b/apps/raycast-extension/src/resume-recording.tsx @@ -0,0 +1,19 @@ +import { showToast, Toast } from "@raycast/api"; +import * as deeplink from "./utils/deeplink"; + +export default async function Command() { + try { + await deeplink.resumeRecording(); + await showToast({ + style: Toast.Style.Success, + title: "Recording Resumed", + message: "Cap recording has been resumed", + }); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to Resume Recording", + message: error instanceof Error ? error.message : "Unknown error occurred", + }); + } +} diff --git a/apps/raycast-extension/src/start-recording.tsx b/apps/raycast-extension/src/start-recording.tsx new file mode 100644 index 0000000000..97ef155201 --- /dev/null +++ b/apps/raycast-extension/src/start-recording.tsx @@ -0,0 +1,86 @@ +import { Action, ActionPanel, Form, showToast, Toast, popToRoot } from "@raycast/api"; +import { useState } from "react"; +import * as deeplink from "./utils/deeplink"; + +interface FormValues { + captureType: "screen" | "window"; + targetName: string; + camera: boolean; + microphone: boolean; + systemAudio: boolean; + mode: "Studio" | "Instant"; +} + +export default function Command() { + const [isLoading, setIsLoading] = useState(false); + + async function handleSubmit(values: FormValues) { + setIsLoading(true); + try { + const captureMode = + values.captureType === "screen" + ? { screen: values.targetName } + : { window: values.targetName }; + + await deeplink.startRecording({ + captureMode, + camera: values.camera ? { Device: "default" } : null, + micLabel: values.microphone ? "default" : null, + captureSystemAudio: values.systemAudio, + mode: values.mode, + }); + + await showToast({ + style: Toast.Style.Success, + title: "Recording Started", + message: `Recording ${values.targetName} in ${values.mode} mode`, + }); + + await popToRoot(); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to Start Recording", + message: error instanceof Error ? error.message : "Unknown error occurred", + }); + } finally { + setIsLoading(false); + } + } + + return ( +
+ + + } + > + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/apps/raycast-extension/src/stop-recording.tsx b/apps/raycast-extension/src/stop-recording.tsx new file mode 100644 index 0000000000..208fffea5e --- /dev/null +++ b/apps/raycast-extension/src/stop-recording.tsx @@ -0,0 +1,19 @@ +import { showToast, Toast, open } from "@raycast/api"; +import * as deeplink from "./utils/deeplink"; + +export default async function Command() { + try { + await deeplink.stopRecording(); + await showToast({ + style: Toast.Style.Success, + title: "Recording Stopped", + message: "Cap recording has been stopped", + }); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to Stop Recording", + message: error instanceof Error ? error.message : "Unknown error occurred", + }); + } +} diff --git a/apps/raycast-extension/src/switch-camera.tsx b/apps/raycast-extension/src/switch-camera.tsx new file mode 100644 index 0000000000..a9c6086f27 --- /dev/null +++ b/apps/raycast-extension/src/switch-camera.tsx @@ -0,0 +1,57 @@ +import { Action, ActionPanel, Form, showToast, Toast, popToRoot } from "@raycast/api"; +import { useState } from "react"; +import * as deeplink from "./utils/deeplink"; + +interface FormValues { + cameraId: string; + enableCamera: boolean; +} + +export default function Command() { + const [isLoading, setIsLoading] = useState(false); + + async function handleSubmit(values: FormValues) { + setIsLoading(true); + try { + const cameraDevice = values.enableCamera && values.cameraId ? { Device: values.cameraId } : null; + + await deeplink.setCamera(cameraDevice); + + await showToast({ + style: Toast.Style.Success, + title: values.enableCamera ? "Camera Switched" : "Camera Disabled", + message: values.enableCamera ? `Switched to camera: ${values.cameraId}` : "Camera has been disabled", + }); + + await popToRoot(); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to Switch Camera", + message: error instanceof Error ? error.message : "Unknown error occurred", + }); + } finally { + setIsLoading(false); + } + } + + return ( +
+ + + } + > + + + + + ); +} diff --git a/apps/raycast-extension/src/switch-microphone.tsx b/apps/raycast-extension/src/switch-microphone.tsx new file mode 100644 index 0000000000..1856f57d35 --- /dev/null +++ b/apps/raycast-extension/src/switch-microphone.tsx @@ -0,0 +1,57 @@ +import { Action, ActionPanel, Form, showToast, Toast, popToRoot } from "@raycast/api"; +import { useState } from "react"; +import * as deeplink from "./utils/deeplink"; + +interface FormValues { + microphoneName: string; + enableMicrophone: boolean; +} + +export default function Command() { + const [isLoading, setIsLoading] = useState(false); + + async function handleSubmit(values: FormValues) { + setIsLoading(true); + try { + const micLabel = values.enableMicrophone && values.microphoneName ? values.microphoneName : null; + + await deeplink.setMicrophone(micLabel); + + await showToast({ + style: Toast.Style.Success, + title: values.enableMicrophone ? "Microphone Switched" : "Microphone Muted", + message: values.enableMicrophone ? `Switched to: ${values.microphoneName}` : "Microphone has been muted", + }); + + await popToRoot(); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to Switch Microphone", + message: error instanceof Error ? error.message : "Unknown error occurred", + }); + } finally { + setIsLoading(false); + } + } + + return ( +
+ + + } + > + + + + + ); +} diff --git a/apps/raycast-extension/src/take-screenshot.tsx b/apps/raycast-extension/src/take-screenshot.tsx new file mode 100644 index 0000000000..7a97ec4ce6 --- /dev/null +++ b/apps/raycast-extension/src/take-screenshot.tsx @@ -0,0 +1,63 @@ +import { Action, ActionPanel, Form, showToast, Toast, popToRoot } from "@raycast/api"; +import { useState } from "react"; +import * as deeplink from "./utils/deeplink"; + +interface FormValues { + captureType: "screen" | "window"; + targetName: string; +} + +export default function Command() { + const [isLoading, setIsLoading] = useState(false); + + async function handleSubmit(values: FormValues) { + setIsLoading(true); + try { + const captureTarget = + values.captureType === "screen" + ? { display: { id: values.targetName } } + : { window: { id: values.targetName } }; + + await deeplink.takeScreenshot(captureTarget); + + await showToast({ + style: Toast.Style.Success, + title: "Screenshot Captured", + message: `Screenshot of ${values.targetName} saved`, + }); + + await popToRoot(); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to Take Screenshot", + message: error instanceof Error ? error.message : "Unknown error occurred", + }); + } finally { + setIsLoading(false); + } + } + + return ( +
+ + + } + > + + + + + + + + ); +} diff --git a/apps/raycast-extension/src/toggle-pause.tsx b/apps/raycast-extension/src/toggle-pause.tsx new file mode 100644 index 0000000000..af42f042a7 --- /dev/null +++ b/apps/raycast-extension/src/toggle-pause.tsx @@ -0,0 +1,19 @@ +import { showToast, Toast } from "@raycast/api"; +import * as deeplink from "./utils/deeplink"; + +export default async function Command() { + try { + await deeplink.togglePauseRecording(); + await showToast({ + style: Toast.Style.Success, + title: "Recording Toggled", + message: "Cap recording pause state toggled", + }); + } catch (error) { + await showToast({ + style: Toast.Style.Failure, + title: "Failed to Toggle Pause", + message: error instanceof Error ? error.message : "Unknown error occurred", + }); + } +} diff --git a/apps/raycast-extension/src/utils/deeplink.ts b/apps/raycast-extension/src/utils/deeplink.ts new file mode 100644 index 0000000000..47b5a39b42 --- /dev/null +++ b/apps/raycast-extension/src/utils/deeplink.ts @@ -0,0 +1,135 @@ +/** + * Utility functions for constructing and triggering Cap deeplinks + */ + +export interface CaptureTarget { + display?: { id: string }; + window?: { id: string }; +} + +export interface DeviceOrModelID { + Device?: string; + ModelID?: string; +} + +/** + * Builds a Cap deeplink URL from an action object + */ +export function buildDeeplinkURL(action: Record): string { + const jsonValue = JSON.stringify(action); + const encodedValue = encodeURIComponent(jsonValue); + return `cap-desktop://action?value=${encodedValue}`; +} + +/** + * Opens a Cap deeplink URL + */ +export async function triggerDeeplink(action: Record): Promise { + const url = buildDeeplinkURL(action); + const { open } = await import("@raycast/api"); + await open(url); +} + +/** + * Start recording deeplink + */ +export async function startRecording(options: { + captureMode: { screen: string } | { window: string }; + camera?: DeviceOrModelID | null; + micLabel?: string | null; + captureSystemAudio?: boolean; + mode?: "Studio" | "Instant"; +}): Promise { + const action = { + startRecording: { + captureMode: options.captureMode, + camera: options.camera ?? null, + micLabel: options.micLabel ?? null, + captureSystemAudio: options.captureSystemAudio ?? false, + mode: options.mode ?? "Studio", + }, + }; + await triggerDeeplink(action); +} + +/** + * Stop recording deeplink + */ +export async function stopRecording(): Promise { + await triggerDeeplink({ stopRecording: {} }); +} + +/** + * Pause recording deeplink + */ +export async function pauseRecording(): Promise { + await triggerDeeplink({ pauseRecording: {} }); +} + +/** + * Resume recording deeplink + */ +export async function resumeRecording(): Promise { + await triggerDeeplink({ resumeRecording: {} }); +} + +/** + * Toggle pause recording deeplink + */ +export async function togglePauseRecording(): Promise { + await triggerDeeplink({ togglePauseRecording: {} }); +} + +/** + * Take screenshot deeplink + */ +export async function takeScreenshot(captureTarget: CaptureTarget): Promise { + const action = { + takeScreenshot: { + captureTarget, + }, + }; + await triggerDeeplink(action); +} + +/** + * Set camera deeplink + */ +export async function setCamera(id: DeviceOrModelID | null): Promise { + await triggerDeeplink({ setCamera: { id } }); +} + +/** + * Set microphone deeplink + */ +export async function setMicrophone(label: string | null): Promise { + await triggerDeeplink({ setMicrophone: { label } }); +} + +/** + * List cameras deeplink (output goes to console) + */ +export async function listCameras(): Promise { + await triggerDeeplink({ listCameras: {} }); +} + +/** + * List microphones deeplink (output goes to console) + */ +export async function listMicrophones(): Promise { + await triggerDeeplink({ listMicrophones: {} }); +} + +/** + * List displays deeplink (output goes to console) + */ +export async function listDisplays(): Promise { + await triggerDeeplink({ listDisplays: {} }); +} + +/** + * List windows deeplink (output goes to console) + */ +export async function listWindows(): Promise { + await triggerDeeplink({ listWindows: {} }); +} diff --git a/apps/raycast-extension/tsconfig.json b/apps/raycast-extension/tsconfig.json new file mode 100644 index 0000000000..83aa6c05c2 --- /dev/null +++ b/apps/raycast-extension/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2021", + "lib": [ + "ES2021" + ], + "module": "commonjs", + "jsx": "react", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules" + ] +} \ No newline at end of file From d25751782dec72efdf470a4edc9206a602587cd6 Mon Sep 17 00:00:00 2001 From: Preet Rank Date: Sat, 24 Jan 2026 00:02:31 +0530 Subject: [PATCH 2/2] fix: address bot review feedback - visibility, snake_case JSON, logging, remove comments --- .../desktop/src-tauri/src/deeplink_actions.rs | 8 +- apps/desktop/src-tauri/src/lib.rs | 4 +- .../raycast-extension/src/start-recording.tsx | 8 +- apps/raycast-extension/src/stop-recording.tsx | 2 +- apps/raycast-extension/src/utils/deeplink.ts | 78 ++++--------------- 5 files changed, 26 insertions(+), 74 deletions(-) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index fab4a5ee7e..81095e2e0f 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -186,7 +186,7 @@ impl DeepLinkAction { let cameras = crate::recording::list_cameras(); let cameras_json = serde_json::to_string(&cameras) .map_err(|e| format!("Failed to serialize cameras: {}", e))?; - eprintln!("Available cameras: {}", cameras_json); + tracing::info!("Available cameras: {}", cameras_json); Ok(()) } DeepLinkAction::ListMicrophones => { @@ -194,7 +194,7 @@ impl DeepLinkAction { let microphones: Vec = MicrophoneFeed::list().keys().cloned().collect(); let mics_json = serde_json::to_string(µphones) .map_err(|e| format!("Failed to serialize microphones: {}", e))?; - eprintln!("Available microphones: {}", mics_json); + tracing::info!("Available microphones: {}", mics_json); Ok(()) } DeepLinkAction::ListDisplays => { @@ -210,7 +210,7 @@ impl DeepLinkAction { .collect(); let displays_json = serde_json::to_string(&displays_data) .map_err(|e| format!("Failed to serialize displays: {}", e))?; - eprintln!("Available displays: {}", displays_json); + tracing::info!("Available displays: {}", displays_json); Ok(()) } DeepLinkAction::ListWindows => { @@ -226,7 +226,7 @@ impl DeepLinkAction { .collect(); let windows_json = serde_json::to_string(&windows_data) .map_err(|e| format!("Failed to serialize windows: {}", e))?; - eprintln!("Available windows: {}", windows_json); + tracing::info!("Available windows: {}", windows_json); Ok(()) } DeepLinkAction::OpenEditor { project_path } => { diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 90803f8abe..5576fc6273 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -382,7 +382,7 @@ impl App { #[tauri::command] #[specta::specta] #[instrument(skip(state))] -async fn set_mic_input(state: MutableState<'_, App>, label: Option) -> Result<(), String> { +pub(crate) async fn set_mic_input(state: MutableState<'_, App>, label: Option) -> Result<(), String> { let (mic_feed, studio_handle, current_label) = { let app = state.read().await; let handle = match app.current_recording() { @@ -468,7 +468,7 @@ fn get_system_diagnostics() -> cap_recording::diagnostics::SystemDiagnostics { #[specta::specta] #[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, diff --git a/apps/raycast-extension/src/start-recording.tsx b/apps/raycast-extension/src/start-recording.tsx index 97ef155201..66d6144983 100644 --- a/apps/raycast-extension/src/start-recording.tsx +++ b/apps/raycast-extension/src/start-recording.tsx @@ -18,14 +18,12 @@ export default function Command() { setIsLoading(true); try { const captureMode = - values.captureType === "screen" - ? { screen: values.targetName } - : { window: values.targetName }; + values.captureType === "screen" ? { screen: values.targetName } : { window: values.targetName }; await deeplink.startRecording({ captureMode, - camera: values.camera ? { Device: "default" } : null, - micLabel: values.microphone ? "default" : null, + camera: null, + micLabel: null, captureSystemAudio: values.systemAudio, mode: values.mode, }); diff --git a/apps/raycast-extension/src/stop-recording.tsx b/apps/raycast-extension/src/stop-recording.tsx index 208fffea5e..b55bfd16ba 100644 --- a/apps/raycast-extension/src/stop-recording.tsx +++ b/apps/raycast-extension/src/stop-recording.tsx @@ -1,4 +1,4 @@ -import { showToast, Toast, open } from "@raycast/api"; +import { showToast, Toast } from "@raycast/api"; import * as deeplink from "./utils/deeplink"; export default async function Command() { diff --git a/apps/raycast-extension/src/utils/deeplink.ts b/apps/raycast-extension/src/utils/deeplink.ts index 47b5a39b42..a687263bff 100644 --- a/apps/raycast-extension/src/utils/deeplink.ts +++ b/apps/raycast-extension/src/utils/deeplink.ts @@ -1,7 +1,3 @@ -/** - * Utility functions for constructing and triggering Cap deeplinks - */ - export interface CaptureTarget { display?: { id: string }; window?: { id: string }; @@ -12,27 +8,18 @@ export interface DeviceOrModelID { ModelID?: string; } -/** - * Builds a Cap deeplink URL from an action object - */ export function buildDeeplinkURL(action: Record): string { const jsonValue = JSON.stringify(action); const encodedValue = encodeURIComponent(jsonValue); return `cap-desktop://action?value=${encodedValue}`; } -/** - * Opens a Cap deeplink URL - */ export async function triggerDeeplink(action: Record): Promise { const url = buildDeeplinkURL(action); const { open } = await import("@raycast/api"); await open(url); } -/** - * Start recording deeplink - */ export async function startRecording(options: { captureMode: { screen: string } | { window: string }; camera?: DeviceOrModelID | null; @@ -41,95 +28,62 @@ export async function startRecording(options: { mode?: "Studio" | "Instant"; }): Promise { const action = { - startRecording: { - captureMode: options.captureMode, + start_recording: { + capture_mode: options.captureMode, camera: options.camera ?? null, - micLabel: options.micLabel ?? null, - captureSystemAudio: options.captureSystemAudio ?? false, + mic_label: options.micLabel ?? null, + capture_system_audio: options.captureSystemAudio ?? false, mode: options.mode ?? "Studio", }, }; await triggerDeeplink(action); } -/** - * Stop recording deeplink - */ export async function stopRecording(): Promise { - await triggerDeeplink({ stopRecording: {} }); + await triggerDeeplink({ stop_recording: {} }); } -/** - * Pause recording deeplink - */ export async function pauseRecording(): Promise { - await triggerDeeplink({ pauseRecording: {} }); + await triggerDeeplink({ pause_recording: {} }); } -/** - * Resume recording deeplink - */ export async function resumeRecording(): Promise { - await triggerDeeplink({ resumeRecording: {} }); + await triggerDeeplink({ resume_recording: {} }); } -/** - * Toggle pause recording deeplink - */ export async function togglePauseRecording(): Promise { - await triggerDeeplink({ togglePauseRecording: {} }); + await triggerDeeplink({ toggle_pause_recording: {} }); } -/** - * Take screenshot deeplink - */ export async function takeScreenshot(captureTarget: CaptureTarget): Promise { const action = { - takeScreenshot: { - captureTarget, + take_screenshot: { + capture_target: captureTarget, }, }; await triggerDeeplink(action); } -/** - * Set camera deeplink - */ export async function setCamera(id: DeviceOrModelID | null): Promise { - await triggerDeeplink({ setCamera: { id } }); + await triggerDeeplink({ set_camera: { id } }); } -/** - * Set microphone deeplink - */ export async function setMicrophone(label: string | null): Promise { - await triggerDeeplink({ setMicrophone: { label } }); + await triggerDeeplink({ set_microphone: { label } }); } -/** - * List cameras deeplink (output goes to console) - */ export async function listCameras(): Promise { - await triggerDeeplink({ listCameras: {} }); + await triggerDeeplink({ list_cameras: {} }); } -/** - * List microphones deeplink (output goes to console) - */ export async function listMicrophones(): Promise { - await triggerDeeplink({ listMicrophones: {} }); + await triggerDeeplink({ list_microphones: {} }); } -/** - * List displays deeplink (output goes to console) - */ export async function listDisplays(): Promise { - await triggerDeeplink({ listDisplays: {} }); + await triggerDeeplink({ list_displays: {} }); } -/** - * List windows deeplink (output goes to console) - */ export async function listWindows(): Promise { - await triggerDeeplink({ listWindows: {} }); + await triggerDeeplink({ list_windows: {} }); }