feat: extend deeplinks with recording controls + Raycast extension#1660
feat: extend deeplinks with recording controls + Raycast extension#1660qiridigital wants to merge 1 commit intoCapSoftware:mainfrom
Conversation
Add new deeplink actions for recording control:
- PauseRecording: pause the current recording
- ResumeRecording: resume a paused recording
- TogglePauseRecording: toggle pause/resume state
- SetCamera: switch camera input by model/device ID
- SetMicrophone: switch microphone input by label
All new actions reuse existing recording control functions
(pause_recording, resume_recording, toggle_pause_recording,
set_camera_input, set_mic_input) and follow the established
cap-desktop://action?value={json} URL scheme with serde
deserialization, requiring zero frontend changes.
Add Raycast extension (apps/raycast/) with 8 commands:
- Start/Stop/Pause/Resume/Toggle recording
- Set Camera, Set Microphone
- Open Settings
Closes CapSoftware#1540
| @@ -0,0 +1,60 @@ | |||
| /** | |||
There was a problem hiding this comment.
Repo-wide guideline is no code comments; can we drop the docblock + inline comment here?
| /** | |
| export function buildDeeplinkUrl(action: DeepLinkAction): string { | |
| const json = JSON.stringify(action); | |
| return `cap-desktop://action?value=${encodeURIComponent(json)}`; | |
| } | |
| export type DeepLinkAction = | |
| | "stop_recording" | |
| | "pause_recording" | |
| | "resume_recording" | |
| | "toggle_pause_recording" | |
| | StartRecordingAction | |
| | SetCameraAction | |
| | SetMicrophoneAction | |
| | OpenEditorAction | |
| | OpenSettingsAction; |
| } | ||
|
|
||
| async function handleSubmit(values: FormValues) { | ||
| const captureMode = |
There was a problem hiding this comment.
Worth validating/normalizing inputs before firing the deeplink; empty captureName or whitespace camera/mic become hard-to-debug errors on the desktop side.
| const captureMode = | |
| async function handleSubmit(values: FormValues) { | |
| const captureName = values.captureName.trim(); | |
| if (!captureName) { | |
| await showHUD("Please enter a screen/window name"); | |
| return; | |
| } | |
| const captureMode = values.captureType === "screen" ? { screen: captureName } : { window: captureName }; | |
| const camera = values.camera.trim() ? { ModelID: values.camera.trim() } : null; | |
| const micLabel = values.mic.trim() || null; | |
| const url = buildDeeplinkUrl({ | |
| start_recording: { | |
| capture_mode: captureMode, | |
| camera, | |
| mic_label: micLabel, | |
| capture_system_audio: values.systemAudio, | |
| mode: values.mode as "studio" | "instant", | |
| }, | |
| }); | |
| await open(url); | |
| await showHUD("Starting Cap recording"); | |
| } |
| import { LaunchProps, open, showHUD } from "@raycast/api"; | ||
| import { buildDeeplinkUrl } from "./utils"; | ||
|
|
||
| interface Arguments { |
There was a problem hiding this comment.
package.json marks this argument as required: false, so the type should allow it to be missing.
| interface Arguments { | |
| interface Arguments { | |
| camera?: string; | |
| } |
| import { LaunchProps, open, showHUD } from "@raycast/api"; | ||
| import { buildDeeplinkUrl } from "./utils"; | ||
|
|
||
| interface Arguments { |
There was a problem hiding this comment.
Same as set-camera: argument is optional in package.json, so the type should reflect that.
| interface Arguments { | |
| interface Arguments { | |
| microphone?: string; | |
| } |
| | Command | Description | Mode | | ||
| |---------|-------------|------| | ||
| | **Start Recording** | Start a screen/window recording with configurable options | Form | | ||
| | **Stop Recording** | Stop the current recording | Instant | |
There was a problem hiding this comment.
Minor docs mismatch: Set Camera / Set Microphone are mode: "view" in apps/raycast/package.json (argument entry), but the table says Instant.
| | **Stop Recording** | Stop the current recording | Instant | | |
| | **Set Camera** | Switch camera input (pass name as argument) | Form | | |
| | **Set Microphone** | Switch microphone input (pass label as argument) | Form | |
|
|
||
| const url = buildDeeplinkUrl({ | ||
| set_camera: { | ||
| camera: camera ? { ModelID: camera } : null, |
There was a problem hiding this comment.
ModelID expects vid:pid format, not a camera name
The Rust ModelID deserializer at crates/camera/src/lib.rs:144 splits on : with an unwrap():
let (vid, pid) = s.split_once(":").unwrap();If a user enters a human-readable name like "FaceTime HD Camera" (which the placeholder text "Camera name or model" suggests), the deeplink will produce {"ModelID": "FaceTime HD Camera"}. Since there's no : in that string, split_once(":").unwrap() will panic and crash the desktop app.
The ModelID type requires a vendor-ID:product-ID string (e.g., "0x1234:0x5678"), not a friendly camera name. Either the Raycast command should explain this format requirement to the user, or it should use DeviceID instead if that accepts friendly names.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast/src/set-camera.tsx
Line: 13
Comment:
**`ModelID` expects `vid:pid` format, not a camera name**
The Rust `ModelID` deserializer at `crates/camera/src/lib.rs:144` splits on `:` with an `unwrap()`:
```rust
let (vid, pid) = s.split_once(":").unwrap();
```
If a user enters a human-readable name like "FaceTime HD Camera" (which the placeholder text `"Camera name or model"` suggests), the deeplink will produce `{"ModelID": "FaceTime HD Camera"}`. Since there's no `:` in that string, `split_once(":").unwrap()` will **panic** and crash the desktop app.
The `ModelID` type requires a vendor-ID:product-ID string (e.g., `"0x1234:0x5678"`), not a friendly camera name. Either the Raycast command should explain this format requirement to the user, or it should use `DeviceID` instead if that accepts friendly names.
How can I resolve this? If you propose a fix, please make it concise.| ? { screen: values.captureName } | ||
| : { window: values.captureName }; | ||
|
|
||
| const camera = values.camera ? { ModelID: values.camera } : null; |
There was a problem hiding this comment.
Same ModelID panic risk as set-camera
This line has the same issue as set-camera.tsx: the form placeholder says "Camera name or model" but wraps the input in { ModelID: values.camera }. The Rust ModelID deserializer expects a "vid:pid" format string and will panic (unwrap() on split_once(":")) if the user enters a human-readable camera name like "FaceTime HD Camera".
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast/src/start-recording.tsx
Line: 44
Comment:
**Same `ModelID` panic risk as set-camera**
This line has the same issue as `set-camera.tsx`: the form placeholder says "Camera name or model" but wraps the input in `{ ModelID: values.camera }`. The Rust `ModelID` deserializer expects a `"vid:pid"` format string and will **panic** (`unwrap()` on `split_once(":")`) if the user enters a human-readable camera name like "FaceTime HD Camera".
How can I resolve this? If you propose a fix, please make it concise.| /** | ||
| * Builds a Cap desktop deeplink URL. | ||
| * | ||
| * Cap desktop uses the URL scheme: cap-desktop://action?value={json_encoded_action} | ||
| * The JSON value follows the Rust serde enum serialization format: | ||
| * - Unit variants: "variant_name" | ||
| * - Struct variants: { "variant_name": { field: value } } | ||
| */ | ||
| export function buildDeeplinkUrl(action: DeepLinkAction): string { | ||
| const json = JSON.stringify(action); | ||
| return `cap-desktop://action?value=${encodeURIComponent(json)}`; | ||
| } | ||
|
|
||
| // Unit variants serialize as plain strings |
There was a problem hiding this comment.
Code comments violate repo convention
Per the project's CLAUDE.md: "CRITICAL: NO CODE COMMENTS: Never add any form of comments to code." This file contains a JSDoc block (lines 1-8) and a single-line comment on line 14 (// Unit variants serialize as plain strings). These should be removed — the types and function names are already self-explanatory.
Rule Used: CLAUDE.md (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast/src/utils.ts
Line: 1-14
Comment:
**Code comments violate repo convention**
Per the project's CLAUDE.md: "CRITICAL: NO CODE COMMENTS: Never add any form of comments to code." This file contains a JSDoc block (lines 1-8) and a single-line comment on line 14 (`// Unit variants serialize as plain strings`). These should be removed — the types and function names are already self-explanatory.
**Rule Used:** CLAUDE.md ([source](https://app.greptile.com/review/custom-context?memory=9a906542-f1fe-42c1-89a2-9f252d96d9f0))
How can I resolve this? If you propose a fix, please make it concise.| { | ||
| "start_recording": { | ||
| "capture_mode": { "screen": "Built-in Retina Display" }, | ||
| "camera": { "ModelID": "FaceTime HD Camera" }, |
There was a problem hiding this comment.
Misleading ModelID example in docs
The example shows "ModelID": "FaceTime HD Camera", but in the Rust codebase ModelID actually deserializes from a "vid:pid" format string (e.g., "0x1234:0x5678"). This applies to both the "Start recording" and "Switch camera" examples (lines 45 and 56). Using a friendly camera name here would crash the desktop app due to an unwrap() panic in the ModelID deserializer.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast/README.md
Line: 45
Comment:
**Misleading `ModelID` example in docs**
The example shows `"ModelID": "FaceTime HD Camera"`, but in the Rust codebase `ModelID` actually deserializes from a `"vid:pid"` format string (e.g., `"0x1234:0x5678"`). This applies to both the "Start recording" and "Switch camera" examples (lines 45 and 56). Using a friendly camera name here would crash the desktop app due to an `unwrap()` panic in the `ModelID` deserializer.
How can I resolve this? If you propose a fix, please make it concise.
Summary
Extends Cap's existing deeplink system with recording control actions and adds a complete Raycast extension.
Deeplink Actions Added
cap-desktop://action?value=%22pause_recording%22cap-desktop://action?value=%22resume_recording%22cap-desktop://action?value=%22toggle_pause_recording%22cap-desktop://action?value={%22set_camera%22:{%22camera%22:{%22ModelID%22:%22vid:pid%22}}}cap-desktop://action?value={%22set_microphone%22:{%22mic_label%22:%22Mic%20Name%22}}Implementation
All new variants are added to the existing
DeepLinkActionRust enum with serde deserialization, reusing the establishedcap-desktop://action?value={json}URL scheme. Each action delegates to existing public functions:pause_recording()/resume_recording()/toggle_pause_recording()fromrecording.rsset_camera_input()/set_mic_input()fromlib.rsZero frontend changes required — the existing
TryFrom<&Url>parser automatically deserializes the new variants via serde.Raycast Extension (
apps/raycast/)8 commands built with
@raycast/api:Files Changed
apps/desktop/src-tauri/src/deeplink_actions.rs— 5 new enum variants + execute handlersapps/raycast/— Complete Raycast extension (8 commands, README, TypeScript types)Closes #1540
/claim #1540
Greptile Summary
Extends Cap's deeplink system with 5 new
DeepLinkActionenum variants (pause, resume, toggle-pause, set-camera, set-microphone) and adds a complete Raycast extension with 8 commands that invoke these deeplinks. The Rust-side changes are clean and follow existing patterns well.ModelIDformat mismatch causes panic — The Raycastset-cameraandstart-recordingcommands pass human-readable camera names (e.g., "FaceTime HD Camera") asModelID, but the RustModelIDdeserializer expects a"vid:pid"format string and callssplit_once(":").unwrap(), which will panic if the input lacks a colon. This affectsset-camera.tsx,start-recording.tsx, and the README examples.utils.ts— The repo convention (CLAUDE.md) prohibits all code comments.utils.tscontains a JSDoc block and a single-line comment that should be removed.Confidence Score: 2/5
apps/raycast/src/set-camera.tsxandapps/raycast/src/start-recording.tsx— both pass camera names wherevid:pidformat is required, causing a runtime panicImportant Files Changed
Sequence Diagram
sequenceDiagram participant R as Raycast Extension participant OS as macOS URL Handler participant T as Tauri Deep Link Handler participant DL as DeepLinkAction participant Rec as recording.rs participant Lib as lib.rs R->>OS: open cap-desktop://action?value=json OS->>T: handle(urls) T->>T: parse URL and deserialize JSON T->>DL: action.execute(app) alt PauseRecording / ResumeRecording / TogglePauseRecording DL->>Rec: pause/resume/toggle recording end alt SetCamera DL->>Lib: set_camera_input(app, state, camera, None) end alt SetMicrophone DL->>Lib: set_mic_input(state, mic_label) end alt StartRecording DL->>Lib: set_camera_input + set_mic_input DL->>Rec: start_recording(app, state, inputs) endPrompt To Fix All With AI
Last reviewed commit: ac714dc
(2/5) Greptile learns from your feedback when you react with thumbs up/down!
Context used: