feat: Extended deeplinks + Raycast extension#1611
feat: Extended deeplinks + Raycast extension#1611janisag07 wants to merge 1 commit intoCapSoftware:mainfrom
Conversation
…d Raycast extension - Add new deeplink actions: PauseRecording, ResumeRecording, TogglePauseRecording, TakeScreenshot, SetMicrophone, SetCamera - Add path-based deeplink routing (cap-desktop://record/stop, etc.) alongside existing legacy JSON format for backward compatibility - Add comprehensive unit tests for deeplink parsing - Add deeplink documentation (DEEPLINKS.md) - Create Raycast extension with commands for all recording controls, device switching, screenshots, and settings - Raycast extension dynamically enumerates displays, microphones, and cameras via system_profiler (no hardcoded device names) Fixes CapSoftware#1540
| let domain = url.domain(); | ||
|
|
||
| // Path-based deeplinks: cap-desktop://record/start, cap-desktop://device/microphone, etc. | ||
| if let Some(d) = domain { | ||
| if let Some(action) = Self::try_from_path(d, url)? { | ||
| return Ok(action); | ||
| } | ||
| } | ||
|
|
||
| // Legacy JSON-based deeplinks: cap-desktop://action?value={...} | ||
| match domain { | ||
| Some(v) if v != "action" => Err(ActionParseFromUrlError::NotAction), | ||
| _ => Err(ActionParseFromUrlError::Invalid), | ||
| }?; |
There was a problem hiding this comment.
Repo guideline here is no code comments; this reads fine without the two // ... lines.
| let domain = url.domain(); | |
| // Path-based deeplinks: cap-desktop://record/start, cap-desktop://device/microphone, etc. | |
| if let Some(d) = domain { | |
| if let Some(action) = Self::try_from_path(d, url)? { | |
| return Ok(action); | |
| } | |
| } | |
| // Legacy JSON-based deeplinks: cap-desktop://action?value={...} | |
| match domain { | |
| Some(v) if v != "action" => Err(ActionParseFromUrlError::NotAction), | |
| _ => Err(ActionParseFromUrlError::Invalid), | |
| }?; | |
| let domain = url.domain(); | |
| if let Some(d) = domain { | |
| if let Some(action) = Self::try_from_path(d, url)? { | |
| return Ok(action); | |
| } | |
| } | |
| match domain { | |
| Some(v) if v != "action" => Err(ActionParseFromUrlError::NotAction), | |
| _ => Err(ActionParseFromUrlError::Invalid), | |
| }?; |
| /// Parse path-based deeplink URLs. | ||
| /// | ||
| /// Supported routes: | ||
| /// - `cap-desktop://record/start?screen=<name>&mode=<studio|instant>` | ||
| /// - `cap-desktop://record/stop` | ||
| /// - `cap-desktop://record/pause` | ||
| /// - `cap-desktop://record/resume` | ||
| /// - `cap-desktop://record/toggle-pause` | ||
| /// - `cap-desktop://record/restart` | ||
| /// - `cap-desktop://screenshot?screen=<name>` | ||
| /// - `cap-desktop://device/microphone?label=<name>` (omit label to disable) | ||
| /// - `cap-desktop://device/camera?device_id=<id>` or `?model_id=<id>` or `?off=true` | ||
| /// - `cap-desktop://settings?page=<page>` | ||
| fn try_from_path(domain: &str, url: &Url) -> Result<Option<Self>, ActionParseFromUrlError> { |
There was a problem hiding this comment.
This adds a big /// block, but the repo guidelines forbid code comments. Since apps/desktop/src-tauri/DEEPLINKS.md exists now, I’d move the route list there and drop this.
| /// Parse path-based deeplink URLs. | |
| /// | |
| /// Supported routes: | |
| /// - `cap-desktop://record/start?screen=<name>&mode=<studio|instant>` | |
| /// - `cap-desktop://record/stop` | |
| /// - `cap-desktop://record/pause` | |
| /// - `cap-desktop://record/resume` | |
| /// - `cap-desktop://record/toggle-pause` | |
| /// - `cap-desktop://record/restart` | |
| /// - `cap-desktop://screenshot?screen=<name>` | |
| /// - `cap-desktop://device/microphone?label=<name>` (omit label to disable) | |
| /// - `cap-desktop://device/camera?device_id=<id>` or `?model_id=<id>` or `?off=true` | |
| /// - `cap-desktop://settings?page=<page>` | |
| fn try_from_path(domain: &str, url: &Url) -> Result<Option<Self>, ActionParseFromUrlError> { | |
| fn try_from_path(domain: &str, url: &Url) -> Result<Option<Self>, ActionParseFromUrlError> { |
| const CAP_SCHEME = "cap-desktop://"; | ||
|
|
||
| /** | ||
| * Open a path-based Cap deeplink. |
There was a problem hiding this comment.
Minor: this repo has a no-code-comments rule; apps/raycast/src/utils.ts introduces several /** */ and // comments that should probably be removed to match that.
| try { | ||
| await open(url.toString()); | ||
| } catch { | ||
| await showHUD("❌ Failed to connect to Cap. Is it running?"); | ||
| throw new Error("Could not open Cap deeplink"); | ||
| } |
There was a problem hiding this comment.
Catching without the error drops useful context; keeping the original error (and including the deeplink) makes failures easier to debug.
| try { | |
| await open(url.toString()); | |
| } catch { | |
| await showHUD("❌ Failed to connect to Cap. Is it running?"); | |
| throw new Error("Could not open Cap deeplink"); | |
| } | |
| try { | |
| await open(url.toString()); | |
| } catch (error) { | |
| await showHUD("Failed to connect to Cap. Is it running?"); | |
| throw new Error(`Could not open Cap deeplink: ${url.toString()}`, { cause: error }); | |
| } |
Summary
Adds comprehensive deeplink support for recording controls, device switching, and screenshots, plus a Raycast extension that uses them.
Fixes #1540
Deeplink Changes (
apps/desktop/src-tauri/src/deeplink_actions.rs)New actions added to
DeepLinkActionenum:PauseRecording— pause the active recordingResumeRecording— resume a paused recordingTogglePauseRecording— toggle pause stateTakeScreenshot— capture a screenshot of a specified display/windowSetMicrophone— switch or disable the active microphoneSetCamera— switch or disable the active camera (by device ID or model ID)New path-based routing alongside legacy JSON format:
cap-desktop://record/start?screen=<name>&mode=studiocap-desktop://record/stopcap-desktop://record/pause/resume/toggle-pausecap-desktop://screenshot?screen=<name>cap-desktop://device/microphone?label=<name>cap-desktop://device/camera?device_id=<id>/?off=truecap-desktop://settings?page=<page>Full backward compatibility with existing
cap-desktop://action?value={...}JSON deeplinks.Error messages include available device/display names for easier debugging when names don't match exactly.
Unit tests: 12 tests covering path-based parsing, legacy JSON, edge cases (missing params, unknown actions, camera disable).
Raycast Extension (
apps/raycast/)Key design decisions:
system_profilerUnique IDfield, not display namesexecuteDeepLinkhelper — consistent error handling across all commandsDocumentation
Added
apps/desktop/src-tauri/DEEPLINKS.mdwith full reference for all deeplink routes and examples.Testing
npx tsc --noEmitpasses for the Raycast extensionGreptile Summary
Extends Cap's deeplink system with path-based routing for recording controls, device switching, and screenshots, plus a new Raycast extension that leverages these deeplinks. The Rust implementation uses proper error handling with helpful debugging messages, maintains backward compatibility with legacy JSON deeplinks, and includes comprehensive unit tests. The Raycast extension dynamically enumerates devices via
system_profilerwithout hardcoded values, uses stable camera IDs for reliability, and provides a clean UX with consistent error handling across all commands.Key improvements:
cap-desktop://record/start) are cleaner than legacy JSON formatDEEPLINKS.mdConfidence Score: 5/5
Important Files Changed
Sequence Diagram
sequenceDiagram participant Raycast participant system_profiler participant Cap Desktop participant Recording Engine Note over Raycast,Cap Desktop: Command Execution Flow Raycast->>system_profiler: Get devices/displays system_profiler-->>Raycast: Device list (JSON) Raycast->>Raycast: Parse devices Note over Raycast: User selects device/display Raycast->>Cap Desktop: cap-desktop://record/start?screen=name&mode=studio Cap Desktop->>Cap Desktop: Parse deeplink URL Cap Desktop->>Cap Desktop: Resolve capture target Cap Desktop->>Recording Engine: Start recording with params Recording Engine-->>Cap Desktop: Recording started Cap Desktop-->>Raycast: Success (via OS) Raycast->>Raycast: Show HUD confirmation Note over Raycast,Cap Desktop: Device Switching Flow Raycast->>Cap Desktop: cap-desktop://device/microphone?label=name Cap Desktop->>Cap Desktop: Parse deeplink Cap Desktop->>Recording Engine: Set microphone input Recording Engine-->>Cap Desktop: Input changed Cap Desktop-->>Raycast: Success (via OS) Raycast->>Raycast: Show HUD confirmationLast reviewed commit: 765e6dc
(2/5) Greptile learns from your feedback when you react with thumbs up/down!