Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ad896bf
feat: Deeplinks Support + Raycast Extension
Excellencedev Jan 24, 2026
a8c5a6f
logic fixes
Excellencedev Jan 24, 2026
540922e
Update apps/raycast-extension/package.json
Excellencedev Jan 24, 2026
c50550f
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
c3f0cf7
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
5c793f9
Update apps/raycast-extension/src/utils.ts
Excellencedev Jan 24, 2026
0c7fa42
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
4036ae5
Update apps/raycast-extension/src/utils.ts
Excellencedev Jan 24, 2026
5380812
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
f79d56a
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
75d77e4
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
8e6beb1
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
e0c15f1
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
3ea5202
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
752bfa9
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
c1c6a46
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
d4760c3
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
4f1d6a8
manual fixes
Excellencedev Jan 24, 2026
3ad5663
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
7360964
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
d44c8c3
Update apps/raycast-extension/src/start-recording.tsx
Excellencedev Jan 24, 2026
6f335ea
Update apps/desktop/src-tauri/src/deeplink_actions.rs
Excellencedev Jan 24, 2026
9a7d1f4
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
1371856
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
eeb2da1
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
8a6c82e
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
12903b9
improve manually
Excellencedev Jan 24, 2026
31af095
Update apps/desktop/src-tauri/src/deeplink_actions.rs
Excellencedev Jan 24, 2026
d5c8eea
Update apps/desktop/src-tauri/src/deeplink_actions.rs
Excellencedev Jan 24, 2026
6b37436
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
896b791
Update apps/desktop/src-tauri/src/deeplink_actions.rs
Excellencedev Jan 24, 2026
1312514
Update apps/desktop/src-tauri/src/deeplink_actions.rs
Excellencedev Jan 24, 2026
02cd15c
Update apps/desktop/src-tauri/src/deeplink_actions.rs
Excellencedev Jan 24, 2026
4a1cc86
Update apps/raycast-extension/README.md
Excellencedev Jan 24, 2026
93dc03d
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
53c9025
Update apps/desktop/src-tauri/src/lib.rs
Excellencedev Jan 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions apps/desktop/src-tauri/src/deeplink_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,24 @@ pub enum DeepLinkAction {
capture_system_audio: bool,
mode: RecordingMode,
},
StartDefaultRecording,
StopRecording,
OpenEditor {
project_path: PathBuf,
},
OpenSettings {
page: Option<String>,
},
PauseRecording,
ResumeRecording,
SetMicrophone {
label: Option<String>,
},
SetCamera {
id: Option<DeviceOrModelID>,
},
CycleMicrophone,
CycleCamera,
}

pub fn handle(app_handle: &AppHandle, urls: Vec<Url>) {
Expand Down Expand Up @@ -143,6 +154,25 @@ impl DeepLinkAction {
.await
.map(|_| ())
}
DeepLinkAction::StartDefaultRecording => {
let displays = cap_recording::screen_capture::list_displays();
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
if let Some((display, _)) = displays.first() {
let state = app.state::<ArcLock<App>>();

let inputs = StartRecordingInputs {
mode: RecordingMode::Instant,
capture_target: ScreenCaptureTarget::Display { id: display.id },
capture_system_audio: false,
organization_id: None,
};

crate::recording::start_recording(app.clone(), state, inputs)
.await
.map(|_| ())
} else {
Err("No displays found".to_string())
}
}
DeepLinkAction::StopRecording => {
crate::recording::stop_recording(app.clone(), app.state()).await
}
Expand All @@ -152,6 +182,30 @@ impl DeepLinkAction {
DeepLinkAction::OpenSettings { page } => {
crate::show_window(app.clone(), ShowCapWindow::Settings { page }).await
}
DeepLinkAction::PauseRecording => {
Comment thread
Excellencedev marked this conversation as resolved.
Comment thread
Excellencedev marked this conversation as resolved.
let state = app.state::<ArcLock<App>>();
crate::pause_recording(state).await
}
Comment thread
Excellencedev marked this conversation as resolved.
DeepLinkAction::ResumeRecording => {
Comment thread
Excellencedev marked this conversation as resolved.
let state = app.state::<ArcLock<App>>();
crate::resume_recording(state).await
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
}
Comment thread
Excellencedev marked this conversation as resolved.
Comment on lines +190 to +200
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: duplicate match arms cause compilation error. Lines 196-200 repeat the PauseRecording and ResumeRecording cases.

Suggested change
DeepLinkAction::PauseRecording => {
crate::recording::pause_recording(app.clone(), app.state()).await
}
DeepLinkAction::ResumeRecording => {
crate::recording::resume_recording(app.clone(), app.state()).await
}
crate::recording::pause_recording(app.clone(), app.state()).await
}
DeepLinkAction::ResumeRecording => {
crate::recording::resume_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::SetMicrophone { label } => {
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/deeplink_actions.rs
Line: 190:200

Comment:
**syntax:** duplicate match arms cause compilation error. Lines 196-200 repeat the `PauseRecording` and `ResumeRecording` cases.

```suggestion
            DeepLinkAction::PauseRecording => {
                crate::recording::pause_recording(app.clone(), app.state()).await
            }
            DeepLinkAction::ResumeRecording => {
                crate::recording::resume_recording(app.clone(), app.state()).await
            }
            DeepLinkAction::SetMicrophone { label } => {
```

How can I resolve this? If you propose a fix, please make it concise.

DeepLinkAction::SetMicrophone { label } => {
let state = app.state::<ArcLock<App>>();
crate::set_mic_input(state, label).await
}
DeepLinkAction::SetCamera { id } => {
let state = app.state::<ArcLock<App>>();
crate::set_camera_input(app.clone(), state, id).await
}
Comment thread
Excellencedev marked this conversation as resolved.
DeepLinkAction::CycleMicrophone => {
let state = app.state::<ArcLock<App>>();
crate::cycle_mic_input(state).await
}
Comment on lines +219 to +222
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: missing permissions check before cycling microphone. Other microphone operations (like SetMicrophone on line 202) check permissions first.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/deeplink_actions.rs
Line: 219:222

Comment:
**style:** missing permissions check before cycling microphone. Other microphone operations (like `SetMicrophone` on line 202) check permissions first.

How can I resolve this? If you propose a fix, please make it concise.

DeepLinkAction::CycleCamera => {
let state = app.state::<ArcLock<App>>();
crate::cycle_camera_input(app.clone(), state).await
}
Comment on lines +223 to +226
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: missing permissions check before cycling camera. Other camera operations (like SetCamera on line 211) check permissions first.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/deeplink_actions.rs
Line: 223:226

Comment:
**style:** missing permissions check before cycling camera. Other camera operations (like `SetCamera` on line 211) check permissions first.

How can I resolve this? If you propose a fix, please make it concise.

}
}
}
93 changes: 93 additions & 0 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,99 @@ async fn set_camera_input(
Ok(())
}

#[tauri::command]
#[specta::specta]
#[instrument(skip(state))]
pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> {
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
let mut app = state.write().await;
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
if let Some(recording) = app.current_recording_mut() {
recording.pause().await.map_err(|e| e.to_string())?;
}
Ok(())
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
}

#[tauri::command]
#[specta::specta]
#[instrument(skip(state))]
pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> {
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
let mut app = state.write().await;
if let Some(recording) = app.current_recording_mut() {
recording.resume().await.map_err(|e| e.to_string())?;
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
}
Ok(())
}

#[tauri::command]
#[specta::specta]
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
Comment thread
Excellencedev marked this conversation as resolved.
#[instrument(skip(state))]
pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> {
Comment thread
Excellencedev marked this conversation as resolved.
let (current_label, mic_list) = {
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
let app = state.read().await;
Comment thread
Excellencedev marked this conversation as resolved.
let mic_list = MicrophoneFeed::list().keys().cloned().collect::<Vec<_>>();
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
(app.selected_mic_label.clone(), mic_list)
};
Comment thread
Excellencedev marked this conversation as resolved.
Outdated

if mic_list.is_empty() {
return Ok(());
}

let next_label = match current_label {
Some(label) => {
let index = mic_list.iter().position(|l| l == &label);
match index {
Some(i) => mic_list.get((i + 1) % mic_list.len()).cloned(),
None => mic_list.first().cloned(),
}
}
None => mic_list.first().cloned(),
};

set_mic_input(state, next_label).await
}

#[tauri::command]
#[specta::specta]
#[instrument(skip(app_handle, state))]
pub async fn cycle_camera_input(
Comment on lines +628 to +631
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: cycle_camera_input not registered in tauri_specta::collect_commands! around line 2673. Add it after cycle_mic_input so it's callable from deeplinks.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src-tauri/src/lib.rs
Line: 628:631

Comment:
**logic:** `cycle_camera_input` not registered in `tauri_specta::collect_commands!` around line 2673. Add it after `cycle_mic_input` so it's callable from deeplinks.

How can I resolve this? If you propose a fix, please make it concise.

app_handle: AppHandle,
state: MutableState<'_, App>
) -> Result<(), String> {
let (current_id, camera_list) = {
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
let app = state.read().await;
Comment thread
Excellencedev marked this conversation as resolved.
let camera_list = cap_camera::list_cameras().collect::<Vec<_>>();
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
(app.selected_camera_id.clone(), camera_list)
};
Comment thread
Excellencedev marked this conversation as resolved.
Outdated

if camera_list.is_empty() {
return Ok(());
}

let next_id = match current_id {
Some(id) => {
let current_dev_id = match id {
DeviceOrModelID::DeviceID(d) => Some(d),
DeviceOrModelID::ModelID(_) => None,
};
Comment thread
Excellencedev marked this conversation as resolved.
Outdated

let index = if let Some(dev_id) = current_dev_id {
camera_list.iter().position(|c| c.device_id() == dev_id)
} else {
None
};

match index {
Some(i) => camera_list.get((i + 1) % camera_list.len()),
None => camera_list.first(),
}
}
None => camera_list.first(),
};

let next_id = next_id.map(|c| DeviceOrModelID::DeviceID(c.device_id().to_string()));

Comment thread
Excellencedev marked this conversation as resolved.
Outdated
set_camera_input(app_handle, state, next_id).await
}

fn spawn_mic_error_handler(app_handle: AppHandle, error_rx: flume::Receiver<StreamError>) {
tokio::spawn(async move {
let state = app_handle.state::<ArcLock<App>>();
Expand Down
19 changes: 19 additions & 0 deletions apps/raycast-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Cap Control Raycast Extension

Control the Cap desktop app from Raycast.

## Commands

- **Start Recording**: Starts a new recording (defaults to first display).
- **Stop Recording**: Stops the current recording.
- **Pause Recording**: Pauses the current recording.
- **Resume Recording**: Resumes the current recording.
- **Switch Camera**: Cycles through available cameras.
- **Switch Microphone**: Cycles through available microphones.

## Installation

1. `cd apps/raycast-extension`
2. `npm install`
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
3. `npm run build`
Comment on lines +18 to +19
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: duplicate build step with conflicting commands. Line 18 says pnpm build but line 19 says npm run build.

Suggested change
3. `pnpm build`
3. `npm run build`
3. `npm run build`
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/raycast-extension/README.md
Line: 18:19

Comment:
**syntax:** duplicate build step with conflicting commands. Line 18 says `pnpm build` but line 19 says `npm run build`.

```suggestion
3. `npm run build`
```

How can I resolve this? If you propose a fix, please make it concise.

4. Import into Raycast via "Import Extension".
Binary file added apps/raycast-extension/assets/command-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
71 changes: 71 additions & 0 deletions apps/raycast-extension/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"$schema": "https://www.raycast.com/schemas/extension.json",
"name": "cap-control",
Comment thread
Excellencedev marked this conversation as resolved.
"title": "Cap Control",
"description": "Control Cap desktop app",
"icon": "command-icon.png",
"author": "CapSoftware",
"categories": [
"Productivity",
"Media"
],
"license": "MIT",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This repo isn't MIT-licensed overall (see root LICENSE), so MIT here is probably misleading. I'd drop the field unless you intend to license this extension differently.

Suggested change
"license": "MIT",
"categories": [
"Productivity",
"Media"
],
"commands": [

"commands": [
{
"name": "start-recording",
"title": "Start Recording",
"description": "Start a new recording",
"mode": "no-view"
},
{
"name": "stop-recording",
"title": "Stop Recording",
"description": "Stop current recording",
"mode": "no-view"
},
{
"name": "pause-recording",
"title": "Pause Recording",
"description": "Pause current recording",
"mode": "no-view"
},
{
"name": "resume-recording",
"title": "Resume Recording",
"description": "Resume current recording",
"mode": "no-view"
},
{
"name": "switch-camera",
"title": "Switch Camera",
"description": "Cycle through available cameras",
"mode": "no-view"
},
{
"name": "switch-microphone",
"title": "Switch Microphone",
"description": "Cycle through available microphones",
"mode": "no-view"
}
],
"dependencies": {
"@raycast/api": "^1.69.0",
"@raycast/utils": "^1.13.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",
"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"
}
}
5 changes: 5 additions & 0 deletions apps/raycast-extension/src/pause-recording.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { sendCapCommand } from "./utils";

export default async function Command() {
await sendCapCommand("pause_recording", "Recording Paused");
}
5 changes: 5 additions & 0 deletions apps/raycast-extension/src/resume-recording.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { sendCapCommand } from "./utils";

export default async function Command() {
await sendCapCommand("resume_recording", "Recording Resumed");
}
5 changes: 5 additions & 0 deletions apps/raycast-extension/src/start-recording.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { sendCapCommand } from "./utils";

export default async function Command() {
await sendCapCommand("start_default_recording", "Recording Started");
}
Comment thread
Excellencedev marked this conversation as resolved.
5 changes: 5 additions & 0 deletions apps/raycast-extension/src/stop-recording.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { sendCapCommand } from "./utils";

export default async function Command() {
await sendCapCommand("stop_recording", "Recording Stopped");
}
5 changes: 5 additions & 0 deletions apps/raycast-extension/src/switch-camera.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { sendCapCommand } from "./utils";

export default async function Command() {
await sendCapCommand("cycle_camera", "Switching Camera...");
}
5 changes: 5 additions & 0 deletions apps/raycast-extension/src/switch-microphone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { sendCapCommand } from "./utils";

export default async function Command() {
await sendCapCommand("cycle_microphone", "Switching Microphone...");
}
16 changes: 16 additions & 0 deletions apps/raycast-extension/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { open, showHUD, closeMainWindow } from "@raycast/api";

export async function sendCapCommand(action: string | object, hudMessage: string) {
Comment thread
Excellencedev marked this conversation as resolved.
Outdated
try {
await closeMainWindow();
// If action is a string, it's a unit variant (e.g. "stop_recording")
// If it's an object, it's a struct variant (e.g. { "start_recording": ... })
const jsonValue = JSON.stringify(action);
const url = `cap-desktop://action?value=${encodeURIComponent(jsonValue)}`;
await open(url);
await showHUD(hudMessage);
} catch (error) {
console.error(error);
await showHUD("Failed to connect to Cap");
}
}
20 changes: 20 additions & 0 deletions apps/raycast-extension/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"lib": [
"ES2023"
],
"module": "commonjs",
"target": "ES2023",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"jsx": "react",
"moduleResolution": "Node",
"resolveJsonModule": true
},
"include": [
"src/**/*"
]
}