From ad896bf195b3cd4f06ebe7df842930dbaecf1b68 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 11:19:07 +0100 Subject: [PATCH 01/36] feat: Deeplinks Support + Raycast Extension --- .../desktop/src-tauri/src/deeplink_actions.rs | 54 ++++++++++ apps/desktop/src-tauri/src/lib.rs | 93 ++++++++++++++++++ apps/raycast-extension/README.md | 19 ++++ .../raycast-extension/assets/command-icon.png | Bin 0 -> 1098 bytes apps/raycast-extension/package.json | 71 +++++++++++++ .../raycast-extension/src/pause-recording.tsx | 5 + .../src/resume-recording.tsx | 5 + .../raycast-extension/src/start-recording.tsx | 5 + apps/raycast-extension/src/stop-recording.tsx | 5 + apps/raycast-extension/src/switch-camera.tsx | 5 + .../src/switch-microphone.tsx | 5 + apps/raycast-extension/src/utils.ts | 16 +++ apps/raycast-extension/tsconfig.json | 20 ++++ 13 files changed, 303 insertions(+) create mode 100644 apps/raycast-extension/README.md create mode 100644 apps/raycast-extension/assets/command-icon.png 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/utils.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..38e15fb9bf 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -25,6 +25,7 @@ pub enum DeepLinkAction { capture_system_audio: bool, mode: RecordingMode, }, + StartDefaultRecording, StopRecording, OpenEditor { project_path: PathBuf, @@ -32,6 +33,16 @@ pub enum DeepLinkAction { OpenSettings { page: Option, }, + PauseRecording, + ResumeRecording, + SetMicrophone { + label: Option, + }, + SetCamera { + id: Option, + }, + CycleMicrophone, + CycleCamera, } pub fn handle(app_handle: &AppHandle, urls: Vec) { @@ -143,6 +154,25 @@ impl DeepLinkAction { .await .map(|_| ()) } + DeepLinkAction::StartDefaultRecording => { + let displays = cap_recording::screen_capture::list_displays(); + if let Some((display, _)) = displays.first() { + let state = app.state::>(); + + 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 } @@ -152,6 +182,30 @@ impl DeepLinkAction { DeepLinkAction::OpenSettings { page } => { crate::show_window(app.clone(), ShowCapWindow::Settings { page }).await } + DeepLinkAction::PauseRecording => { + let state = app.state::>(); + crate::pause_recording(state).await + } + DeepLinkAction::ResumeRecording => { + let state = app.state::>(); + crate::resume_recording(state).await + } + DeepLinkAction::SetMicrophone { label } => { + let state = app.state::>(); + crate::set_mic_input(state, label).await + } + DeepLinkAction::SetCamera { id } => { + let state = app.state::>(); + crate::set_camera_input(app.clone(), state, id).await + } + DeepLinkAction::CycleMicrophone => { + let state = app.state::>(); + crate::cycle_mic_input(state).await + } + DeepLinkAction::CycleCamera => { + let state = app.state::>(); + crate::cycle_camera_input(app.clone(), state).await + } } } } diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 90803f8abe..927e5a85a2 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -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> { + let mut app = state.write().await; + if let Some(recording) = app.current_recording_mut() { + recording.pause().await.map_err(|e| e.to_string())?; + } + Ok(()) +} + +#[tauri::command] +#[specta::specta] +#[instrument(skip(state))] +pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> { + let mut app = state.write().await; + if let Some(recording) = app.current_recording_mut() { + recording.resume().await.map_err(|e| e.to_string())?; + } + Ok(()) +} + +#[tauri::command] +#[specta::specta] +#[instrument(skip(state))] +pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> { + let (current_label, mic_list) = { + let app = state.read().await; + let mic_list = MicrophoneFeed::list().keys().cloned().collect::>(); + (app.selected_mic_label.clone(), mic_list) + }; + + 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( + app_handle: AppHandle, + state: MutableState<'_, App> +) -> Result<(), String> { + let (current_id, camera_list) = { + let app = state.read().await; + let camera_list = cap_camera::list_cameras().collect::>(); + (app.selected_camera_id.clone(), camera_list) + }; + + 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, + }; + + 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())); + + set_camera_input(app_handle, state, next_id).await +} + fn spawn_mic_error_handler(app_handle: AppHandle, error_rx: flume::Receiver) { tokio::spawn(async move { let state = app_handle.state::>(); diff --git a/apps/raycast-extension/README.md b/apps/raycast-extension/README.md new file mode 100644 index 0000000000..ba6292be42 --- /dev/null +++ b/apps/raycast-extension/README.md @@ -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` +3. `npm run build` +4. Import into Raycast via "Import Extension". diff --git a/apps/raycast-extension/assets/command-icon.png b/apps/raycast-extension/assets/command-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..86ba70e21f2183f48c3558b9cde4e87269a6a142 GIT binary patch literal 1098 zcmV-Q1hxB#P)CRi^E4kw(w)_<+N?uitjl%upG-kJd_I+}FE zyFyp;LzANkNBRO3!cJ9V=<4d))YjIv%sC?Z9Hs8!g@J*At35qECqhOt%m!5FSo2CGVA4wN(+k0fpOl?v;vG0*4o$GN$;7PEXET)rzEJT09cmR^jR>x|2JYr1?(Xj1OirLX@})+?`Kn0@?msRaIwiev?w#Kvtym=e>E6vh*!kEy z>EVcZ<>Y|We?!{7LE5$1Fk)CmuL}dvJ)MNfZ1>9d9+3{atoUt?wC+E5v%jQSE;y5k ziq0*N*8b}}bx%5XQ`*-fJ$WhZ*;>X5=?a#Zj~_Qj`GQ}iEi0r&^PCsP&DDIHGt2m2 zzm@JkcM0f1(XrIdj$Jg_c|V&czzk7TS+`O)vY6v`ZjSPWa}8u&&fK4*rSmNe{Wpyl znWN_n1J95XxNipzytR%+BX2{-qr^3Buf>P?6aXQx@NrwXhybx z1Qi=FprzT`D;>_)d86cIzyOPRQFjiII2cS_r&HEs8YN_q1*15HS+@Zz7hAU@ao)Oe zdiu(Me5q9>yUVb^3^lP}75GhtDaJ@;sxesT?X*`q+w;+c;qKOKd41RI<|yCR zZWtc3Fj>O@w%24Ra4Ykik;+udUy`oewH&FokPvg+&dt5@_;Qof75ROT)j8gV;+qUW zj8wmpow`UrHa_uqrL3qXtRiDlMgp%k^IOQd)}7H(bk0Z2aH@ z2QXkI0#S^z*(l0nB9}9@A9&zRP!pI*<6>8T Qxc~qF07*qoM6N<$g1ZbFdH?_b literal 0 HcmV?d00001 diff --git a/apps/raycast-extension/package.json b/apps/raycast-extension/package.json new file mode 100644 index 0000000000..587729c36a --- /dev/null +++ b/apps/raycast-extension/package.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://www.raycast.com/schemas/extension.json", + "name": "cap-control", + "title": "Cap Control", + "description": "Control Cap desktop app", + "icon": "command-icon.png", + "author": "CapSoftware", + "categories": [ + "Productivity", + "Media" + ], + "license": "MIT", + "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" + } +} \ 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..7612cb8b58 --- /dev/null +++ b/apps/raycast-extension/src/pause-recording.tsx @@ -0,0 +1,5 @@ +import { sendCapCommand } from "./utils"; + +export default async function Command() { + await sendCapCommand("pause_recording", "Recording Paused"); +} diff --git a/apps/raycast-extension/src/resume-recording.tsx b/apps/raycast-extension/src/resume-recording.tsx new file mode 100644 index 0000000000..09e65b2460 --- /dev/null +++ b/apps/raycast-extension/src/resume-recording.tsx @@ -0,0 +1,5 @@ +import { sendCapCommand } from "./utils"; + +export default async function Command() { + await sendCapCommand("resume_recording", "Recording Resumed"); +} diff --git a/apps/raycast-extension/src/start-recording.tsx b/apps/raycast-extension/src/start-recording.tsx new file mode 100644 index 0000000000..f9c569b943 --- /dev/null +++ b/apps/raycast-extension/src/start-recording.tsx @@ -0,0 +1,5 @@ +import { sendCapCommand } from "./utils"; + +export default async function Command() { + await sendCapCommand("start_default_recording", "Recording Started"); +} diff --git a/apps/raycast-extension/src/stop-recording.tsx b/apps/raycast-extension/src/stop-recording.tsx new file mode 100644 index 0000000000..bf661f938c --- /dev/null +++ b/apps/raycast-extension/src/stop-recording.tsx @@ -0,0 +1,5 @@ +import { sendCapCommand } from "./utils"; + +export default async function Command() { + await sendCapCommand("stop_recording", "Recording Stopped"); +} diff --git a/apps/raycast-extension/src/switch-camera.tsx b/apps/raycast-extension/src/switch-camera.tsx new file mode 100644 index 0000000000..aa2863f500 --- /dev/null +++ b/apps/raycast-extension/src/switch-camera.tsx @@ -0,0 +1,5 @@ +import { sendCapCommand } from "./utils"; + +export default async function Command() { + await sendCapCommand("cycle_camera", "Switching Camera..."); +} diff --git a/apps/raycast-extension/src/switch-microphone.tsx b/apps/raycast-extension/src/switch-microphone.tsx new file mode 100644 index 0000000000..b41d5b7ea1 --- /dev/null +++ b/apps/raycast-extension/src/switch-microphone.tsx @@ -0,0 +1,5 @@ +import { sendCapCommand } from "./utils"; + +export default async function Command() { + await sendCapCommand("cycle_microphone", "Switching Microphone..."); +} diff --git a/apps/raycast-extension/src/utils.ts b/apps/raycast-extension/src/utils.ts new file mode 100644 index 0000000000..fb54846c63 --- /dev/null +++ b/apps/raycast-extension/src/utils.ts @@ -0,0 +1,16 @@ +import { open, showHUD, closeMainWindow } from "@raycast/api"; + +export async function sendCapCommand(action: string | object, hudMessage: string) { + 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"); + } +} diff --git a/apps/raycast-extension/tsconfig.json b/apps/raycast-extension/tsconfig.json new file mode 100644 index 0000000000..bf207e6ca1 --- /dev/null +++ b/apps/raycast-extension/tsconfig.json @@ -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/**/*" + ] +} \ No newline at end of file From a8c5a6f2a8ca232773417706474a09e556636f2d Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 11:43:58 +0100 Subject: [PATCH 02/36] logic fixes --- .../desktop/src-tauri/src/deeplink_actions.rs | 19 +++++--- apps/desktop/src-tauri/src/lib.rs | 43 ++++++++++--------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index 38e15fb9bf..f3a760daa2 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -155,23 +155,28 @@ impl DeepLinkAction { .map(|_| ()) } DeepLinkAction::StartDefaultRecording => { - let displays = cap_recording::screen_capture::list_displays(); - if let Some((display, _)) = displays.first() { + let permissions = crate::permissions::do_permissions_check(false); + if !permissions.screen_recording.permitted() { + return Err("Screen recording permission denied".to_string()); + } + + let displays = cap_recording::screen_capture::list_displays(); + if let Some((display, _)) = displays.first() { let state = app.state::>(); - + let inputs = StartRecordingInputs { mode: RecordingMode::Instant, capture_target: ScreenCaptureTarget::Display { id: display.id }, - capture_system_audio: false, + capture_system_audio: false, organization_id: None, }; crate::recording::start_recording(app.clone(), state, inputs) .await .map(|_| ()) - } else { - Err("No displays found".to_string()) - } + } else { + Err("No displays found".to_string()) + } } DeepLinkAction::StopRecording => { crate::recording::stop_recording(app.clone(), app.state()).await diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 927e5a85a2..c8eefcc350 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -614,6 +614,13 @@ pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String #[specta::specta] #[instrument(skip(state))] pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> { + if !permissions::do_permissions_check(false) + .microphone + .permitted() + { + return Ok(()); + } + let (current_label, mic_list) = { let app = state.read().await; let mic_list = MicrophoneFeed::list().keys().cloned().collect::>(); @@ -643,40 +650,36 @@ pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> #[instrument(skip(app_handle, state))] pub async fn cycle_camera_input( app_handle: AppHandle, - state: MutableState<'_, App> + state: MutableState<'_, App>, ) -> Result<(), String> { + if !permissions::do_permissions_check(false).camera.permitted() { + return Ok(()); + } + let (current_id, camera_list) = { let app = state.read().await; let camera_list = cap_camera::list_cameras().collect::>(); (app.selected_camera_id.clone(), camera_list) }; - + 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, - }; - - let index = if let Some(dev_id) = current_dev_id { - camera_list.iter().position(|c| c.device_id() == dev_id) - } else { - None - }; + let current_index = match current_id { + Some(id) => camera_list.iter().position(|c| match &id { + DeviceOrModelID::DeviceID(dev_id) => c.device_id() == dev_id, + DeviceOrModelID::ModelID(mod_id) => c.model_id().map(|m| &m == mod_id).unwrap_or(false), + }), + None => None, + }; - match index { - Some(i) => camera_list.get((i + 1) % camera_list.len()), - None => camera_list.first(), - } - } + let next_camera = match current_index { + Some(i) => camera_list.get((i + 1) % camera_list.len()), None => camera_list.first(), }; - let next_id = next_id.map(|c| DeviceOrModelID::DeviceID(c.device_id().to_string())); + let next_id = next_camera.map(|c| DeviceOrModelID::DeviceID(c.device_id().to_string())); set_camera_input(app_handle, state, next_id).await } From 540922e4f303a8b6c0547f3842e5af5056bde403 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 11:51:03 +0100 Subject: [PATCH 03/36] Update apps/raycast-extension/package.json Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/raycast-extension/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/raycast-extension/package.json b/apps/raycast-extension/package.json index 587729c36a..d5375de1c0 100644 --- a/apps/raycast-extension/package.json +++ b/apps/raycast-extension/package.json @@ -1,6 +1,8 @@ { "$schema": "https://www.raycast.com/schemas/extension.json", "name": "cap-control", + "version": "0.0.0", + "private": true, "title": "Cap Control", "description": "Control Cap desktop app", "icon": "command-icon.png", From c50550fa88cffff94b7290c8f1dce74743e7db36 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 11:51:16 +0100 Subject: [PATCH 04/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index c8eefcc350..3c497dcb58 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -596,7 +596,12 @@ pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> if let Some(recording) = app.current_recording_mut() { recording.pause().await.map_err(|e| e.to_string())?; } - Ok(()) + if let Some(recording) = app.current_recording_mut() { + recording.pause().await.map_err(|e| e.to_string())?; + Ok(()) + } else { + Err("No active recording".to_string()) + } } #[tauri::command] From c3f0cf72150019e8ea0424717a44d87a7542205b Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 11:51:26 +0100 Subject: [PATCH 05/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 3c497dcb58..570806a52b 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -628,7 +628,8 @@ pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> let (current_label, mic_list) = { let app = state.read().await; - let mic_list = MicrophoneFeed::list().keys().cloned().collect::>(); + let mut mic_list = MicrophoneFeed::list().keys().cloned().collect::>(); + mic_list.sort_unstable(); (app.selected_mic_label.clone(), mic_list) }; From 5c793f9675420dfdb7b10641ce9480334404f401 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 11:51:37 +0100 Subject: [PATCH 06/36] Update apps/raycast-extension/src/utils.ts Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/raycast-extension/src/utils.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/raycast-extension/src/utils.ts b/apps/raycast-extension/src/utils.ts index fb54846c63..a4f8cbec7d 100644 --- a/apps/raycast-extension/src/utils.ts +++ b/apps/raycast-extension/src/utils.ts @@ -1,6 +1,20 @@ import { open, showHUD, closeMainWindow } from "@raycast/api"; -export async function sendCapCommand(action: string | object, hudMessage: string) { +import { closeMainWindow, open, showHUD } from "@raycast/api"; + +type CapAction = string | Record; + +export async function sendCapCommand(action: CapAction, hudMessage: string): Promise { + try { + await closeMainWindow(); + const url = `cap-desktop://action?value=${encodeURIComponent(JSON.stringify(action))}`; + await open(url); + await showHUD(hudMessage); + } catch (error) { + console.error(error); + await showHUD("Failed to connect to Cap"); + } +} try { await closeMainWindow(); // If action is a string, it's a unit variant (e.g. "stop_recording") From 0c7fa42fcebc0b7bfd2e65f2283b431d6f168b98 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 11:57:37 +0100 Subject: [PATCH 07/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 570806a52b..ab045febcf 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -593,15 +593,10 @@ async fn set_camera_input( #[instrument(skip(state))] pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> { let mut app = state.write().await; - if let Some(recording) = app.current_recording_mut() { - recording.pause().await.map_err(|e| e.to_string())?; - } - if let Some(recording) = app.current_recording_mut() { - recording.pause().await.map_err(|e| e.to_string())?; - Ok(()) - } else { - Err("No active recording".to_string()) - } + let recording = app + .current_recording_mut() + .ok_or_else(|| "No active recording".to_string())?; + recording.pause().await.map_err(|e| e.to_string()) } #[tauri::command] From 4036ae53f72ac5a7c17deb6b782b9a58a016b9d2 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 11:57:47 +0100 Subject: [PATCH 08/36] Update apps/raycast-extension/src/utils.ts Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/raycast-extension/src/utils.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/apps/raycast-extension/src/utils.ts b/apps/raycast-extension/src/utils.ts index a4f8cbec7d..adc05fcb54 100644 --- a/apps/raycast-extension/src/utils.ts +++ b/apps/raycast-extension/src/utils.ts @@ -1,5 +1,3 @@ -import { open, showHUD, closeMainWindow } from "@raycast/api"; - import { closeMainWindow, open, showHUD } from "@raycast/api"; type CapAction = string | Record; @@ -14,17 +12,4 @@ export async function sendCapCommand(action: CapAction, hudMessage: string): Pro console.error(error); await showHUD("Failed to connect to Cap"); } -} - 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"); - } } From 53808128fc4c81ff8d7637cdc943532cd4a5bec2 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 11:58:00 +0100 Subject: [PATCH 09/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index ab045febcf..910911d0e9 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -670,7 +670,7 @@ pub async fn cycle_camera_input( let current_index = match current_id { Some(id) => camera_list.iter().position(|c| match &id { DeviceOrModelID::DeviceID(dev_id) => c.device_id() == dev_id, - DeviceOrModelID::ModelID(mod_id) => c.model_id().map(|m| &m == mod_id).unwrap_or(false), + DeviceOrModelID::ModelID(mod_id) => c.model_id().is_some_and(|m| m == mod_id), }), None => None, }; From f79d56a5859c4704a75b4036cef949f4b626415c Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 11:58:51 +0100 Subject: [PATCH 10/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 910911d0e9..5ff611fe11 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -602,16 +602,18 @@ pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> #[tauri::command] #[specta::specta] #[instrument(skip(state))] +pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> { + let mut app = state.write().await; + if let Some(recording) = app.current_recording_mut() { pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> { let mut app = state.write().await; if let Some(recording) = app.current_recording_mut() { recording.resume().await.map_err(|e| e.to_string())?; + Ok(()) + } else { + Err("No active recording".to_string()) } - Ok(()) } - -#[tauri::command] -#[specta::specta] #[instrument(skip(state))] pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> { if !permissions::do_permissions_check(false) From 75d77e4642a01723075515ed7e60a34249dc113f Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 11:59:05 +0100 Subject: [PATCH 11/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 5ff611fe11..814cb26150 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -591,16 +591,16 @@ async fn set_camera_input( #[tauri::command] #[specta::specta] #[instrument(skip(state))] +pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> { pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> { let mut app = state.write().await; - let recording = app - .current_recording_mut() - .ok_or_else(|| "No active recording".to_string())?; - recording.pause().await.map_err(|e| e.to_string()) + if let Some(recording) = app.current_recording_mut() { + recording.pause().await.map_err(|e| e.to_string())?; + Ok(()) + } else { + Err("No active recording".to_string()) + } } - -#[tauri::command] -#[specta::specta] #[instrument(skip(state))] pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> { let mut app = state.write().await; From 8e6beb16568d358c0db7afbfea4b775aa59e5974 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:06:04 +0100 Subject: [PATCH 12/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 814cb26150..1cd33c0e71 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -614,6 +614,8 @@ pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String Err("No active recording".to_string()) } } +#[tauri::command] +#[specta::specta] #[instrument(skip(state))] pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> { if !permissions::do_permissions_check(false) From e0c15f116c399098e5a80c41e915dfda25e59387 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:06:18 +0100 Subject: [PATCH 13/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 1cd33c0e71..ed425f4941 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -591,7 +591,6 @@ async fn set_camera_input( #[tauri::command] #[specta::specta] #[instrument(skip(state))] -pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> { pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> { let mut app = state.write().await; if let Some(recording) = app.current_recording_mut() { @@ -601,10 +600,10 @@ pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> Err("No active recording".to_string()) } } + +#[tauri::command] +#[specta::specta] #[instrument(skip(state))] -pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> { - let mut app = state.write().await; - if let Some(recording) = app.current_recording_mut() { pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> { let mut app = state.write().await; if let Some(recording) = app.current_recording_mut() { From 3ea5202efe1c6bb3c2df395040345188eb0e4c37 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:07:23 +0100 Subject: [PATCH 14/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index ed425f4941..de4c4f396a 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -592,7 +592,6 @@ async fn set_camera_input( #[specta::specta] #[instrument(skip(state))] pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> { - let mut app = state.write().await; if let Some(recording) = app.current_recording_mut() { recording.pause().await.map_err(|e| e.to_string())?; Ok(()) From 752bfa9095eb59a4db6eeb0c9b8c7b9115631140 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:07:39 +0100 Subject: [PATCH 15/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index de4c4f396a..2e31a5e2ca 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -660,7 +660,12 @@ pub async fn cycle_camera_input( } let (current_id, camera_list) = { + let current_id = { let app = state.read().await; + app.selected_camera_id.clone() + }; + + let camera_list = cap_camera::list_cameras().collect::>(); let camera_list = cap_camera::list_cameras().collect::>(); (app.selected_camera_id.clone(), camera_list) }; From c1c6a46262c049a9da842b26a962c38435e6129a Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:07:53 +0100 Subject: [PATCH 16/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 2e31a5e2ca..6fea25679f 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -624,7 +624,13 @@ pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> } let (current_label, mic_list) = { + let current_label = { let app = state.read().await; + app.selected_mic_label.clone() + }; + + let mut mic_list = MicrophoneFeed::list().keys().cloned().collect::>(); + mic_list.sort_unstable(); let mut mic_list = MicrophoneFeed::list().keys().cloned().collect::>(); mic_list.sort_unstable(); (app.selected_mic_label.clone(), mic_list) From d4760c3a87bd44605b2ee5ebbd3eb1cd2012d83b Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:08:08 +0100 Subject: [PATCH 17/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 6fea25679f..4f90df6833 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -606,7 +606,6 @@ pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> { let mut app = state.write().await; if let Some(recording) = app.current_recording_mut() { - recording.resume().await.map_err(|e| e.to_string())?; Ok(()) } else { Err("No active recording".to_string()) From 4f1d6a83f2d6ca291768565679f267054a33f729 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:14:40 +0100 Subject: [PATCH 18/36] manual fixes --- apps/desktop/src-tauri/src/lib.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 4f90df6833..50b866f0fd 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -592,6 +592,7 @@ async fn set_camera_input( #[specta::specta] #[instrument(skip(state))] pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> { + let mut app = state.write().await; if let Some(recording) = app.current_recording_mut() { recording.pause().await.map_err(|e| e.to_string())?; Ok(()) @@ -606,6 +607,7 @@ pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> { let mut app = state.write().await; if let Some(recording) = app.current_recording_mut() { + recording.resume().await.map_err(|e| e.to_string())?; Ok(()) } else { Err("No active recording".to_string()) @@ -622,7 +624,6 @@ pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> return Ok(()); } - let (current_label, mic_list) = { let current_label = { let app = state.read().await; app.selected_mic_label.clone() @@ -630,10 +631,6 @@ pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> let mut mic_list = MicrophoneFeed::list().keys().cloned().collect::>(); mic_list.sort_unstable(); - let mut mic_list = MicrophoneFeed::list().keys().cloned().collect::>(); - mic_list.sort_unstable(); - (app.selected_mic_label.clone(), mic_list) - }; if mic_list.is_empty() { return Ok(()); @@ -664,16 +661,12 @@ pub async fn cycle_camera_input( return Ok(()); } - let (current_id, camera_list) = { let current_id = { let app = state.read().await; app.selected_camera_id.clone() }; let camera_list = cap_camera::list_cameras().collect::>(); - let camera_list = cap_camera::list_cameras().collect::>(); - (app.selected_camera_id.clone(), camera_list) - }; if camera_list.is_empty() { return Ok(()); From 3ad566344863176cc169468a06ace82e620f10c5 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:15:56 +0100 Subject: [PATCH 19/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 50b866f0fd..126c9c6417 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -605,6 +605,13 @@ pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> #[specta::specta] #[instrument(skip(state))] pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> { + let app = state.read().await; + let Some(recording) = app.current_recording() else { + return Err("No active recording".to_string()); + }; + + recording.resume().await.map_err(|e| e.to_string()) +} let mut app = state.write().await; if let Some(recording) = app.current_recording_mut() { recording.resume().await.map_err(|e| e.to_string())?; From 73609647971f7ef1653b3dd99f5466ac471bf14b Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:16:12 +0100 Subject: [PATCH 20/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 126c9c6417..b751116855 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -592,6 +592,13 @@ async fn set_camera_input( #[specta::specta] #[instrument(skip(state))] pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> { + let app = state.read().await; + let Some(recording) = app.current_recording() else { + return Err("No active recording".to_string()); + }; + + recording.pause().await.map_err(|e| e.to_string()) +} let mut app = state.write().await; if let Some(recording) = app.current_recording_mut() { recording.pause().await.map_err(|e| e.to_string())?; From d44c8c3a9951f7d69301a61db74c793f61ff94ae Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:29:43 +0100 Subject: [PATCH 21/36] Update apps/raycast-extension/src/start-recording.tsx Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/raycast-extension/src/start-recording.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/raycast-extension/src/start-recording.tsx b/apps/raycast-extension/src/start-recording.tsx index f9c569b943..4052f45e1d 100644 --- a/apps/raycast-extension/src/start-recording.tsx +++ b/apps/raycast-extension/src/start-recording.tsx @@ -1,5 +1,5 @@ import { sendCapCommand } from "./utils"; export default async function Command() { - await sendCapCommand("start_default_recording", "Recording Started"); + await sendCapCommand("start_default_recording", "Recording Started"); } From 6f335ea80806614e271ee5679d496f1582364f09 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:30:09 +0100 Subject: [PATCH 22/36] Update apps/desktop/src-tauri/src/deeplink_actions.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/deeplink_actions.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index f3a760daa2..e745fccd24 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -196,10 +196,20 @@ impl DeepLinkAction { crate::resume_recording(state).await } DeepLinkAction::SetMicrophone { label } => { + let permissions = crate::permissions::do_permissions_check(false); + if !permissions.microphone.permitted() { + return Err("Microphone permission denied".to_string()); + } + let state = app.state::>(); crate::set_mic_input(state, label).await } DeepLinkAction::SetCamera { id } => { + let permissions = crate::permissions::do_permissions_check(false); + if !permissions.camera.permitted() { + return Err("Camera permission denied".to_string()); + } + let state = app.state::>(); crate::set_camera_input(app.clone(), state, id).await } From 9a7d1f455d0d5d4245169d9b6436134e8f9a76e1 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:38:12 +0100 Subject: [PATCH 23/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index b751116855..1b79bdbc03 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -588,6 +588,9 @@ async fn set_camera_input( Ok(()) } +#[tauri::command] +#[specta::specta] +#[instrument(skip(state))] #[tauri::command] #[specta::specta] #[instrument(skip(state))] @@ -599,6 +602,25 @@ pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> recording.pause().await.map_err(|e| e.to_string()) } + +#[tauri::command] +#[specta::specta] +#[instrument(skip(state))] +pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> { + let app = state.read().await; + let Some(recording) = app.current_recording() else { + return Err("No active recording".to_string()); + }; + + recording.resume().await.map_err(|e| e.to_string()) +} + let app = state.read().await; + let Some(recording) = app.current_recording() else { + return Err("No active recording".to_string()); + }; + + recording.pause().await.map_err(|e| e.to_string()) +} let mut app = state.write().await; if let Some(recording) = app.current_recording_mut() { recording.pause().await.map_err(|e| e.to_string())?; From 137185666223173c674d9655e9f4e8af1e9c2e9e Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:49:20 +0100 Subject: [PATCH 24/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 1b79bdbc03..b176179138 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -588,9 +588,6 @@ async fn set_camera_input( Ok(()) } -#[tauri::command] -#[specta::specta] -#[instrument(skip(state))] #[tauri::command] #[specta::specta] #[instrument(skip(state))] From eeb2da1053b2af6c054684e08fd73ee0adf30e53 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:49:39 +0100 Subject: [PATCH 25/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index b176179138..bf759a7965 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -611,21 +611,6 @@ pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String recording.resume().await.map_err(|e| e.to_string()) } - let app = state.read().await; - let Some(recording) = app.current_recording() else { - return Err("No active recording".to_string()); - }; - - recording.pause().await.map_err(|e| e.to_string()) -} - let mut app = state.write().await; - if let Some(recording) = app.current_recording_mut() { - recording.pause().await.map_err(|e| e.to_string())?; - Ok(()) - } else { - Err("No active recording".to_string()) - } -} #[tauri::command] #[specta::specta] From 8a6c82e5c675f2992d7761b9012cbfe90a4785f0 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:49:52 +0100 Subject: [PATCH 26/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index bf759a7965..7f90befab7 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -612,25 +612,6 @@ pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String recording.resume().await.map_err(|e| e.to_string()) } -#[tauri::command] -#[specta::specta] -#[instrument(skip(state))] -pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> { - let app = state.read().await; - let Some(recording) = app.current_recording() else { - return Err("No active recording".to_string()); - }; - - recording.resume().await.map_err(|e| e.to_string()) -} - let mut app = state.write().await; - if let Some(recording) = app.current_recording_mut() { - recording.resume().await.map_err(|e| e.to_string())?; - Ok(()) - } else { - Err("No active recording".to_string()) - } -} #[tauri::command] #[specta::specta] #[instrument(skip(state))] From 12903b945a01b918a050b1efdc52bfc06ca69bfb Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:55:10 +0100 Subject: [PATCH 27/36] improve manually --- .../desktop/src-tauri/src/deeplink_actions.rs | 4 ++-- apps/desktop/src-tauri/src/lib.rs | 24 ------------------- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index e745fccd24..42b3d31435 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -189,11 +189,11 @@ impl DeepLinkAction { } DeepLinkAction::PauseRecording => { let state = app.state::>(); - crate::pause_recording(state).await + crate::recording::pause_recording(app.clone(), state).await } DeepLinkAction::ResumeRecording => { let state = app.state::>(); - crate::resume_recording(state).await + crate::recording::resume_recording(app.clone(), state).await } DeepLinkAction::SetMicrophone { label } => { let permissions = crate::permissions::do_permissions_check(false); diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 7f90befab7..cec2e42c4e 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -588,30 +588,6 @@ async fn set_camera_input( Ok(()) } -#[tauri::command] -#[specta::specta] -#[instrument(skip(state))] -pub async fn pause_recording(state: MutableState<'_, App>) -> Result<(), String> { - let app = state.read().await; - let Some(recording) = app.current_recording() else { - return Err("No active recording".to_string()); - }; - - recording.pause().await.map_err(|e| e.to_string()) -} - -#[tauri::command] -#[specta::specta] -#[instrument(skip(state))] -pub async fn resume_recording(state: MutableState<'_, App>) -> Result<(), String> { - let app = state.read().await; - let Some(recording) = app.current_recording() else { - return Err("No active recording".to_string()); - }; - - recording.resume().await.map_err(|e| e.to_string()) -} - #[tauri::command] #[specta::specta] #[instrument(skip(state))] From 31af095a1eaf9be5782902f3017c2e46825bad52 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:58:27 +0100 Subject: [PATCH 28/36] Update apps/desktop/src-tauri/src/deeplink_actions.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/deeplink_actions.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index 42b3d31435..befe07695d 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -188,6 +188,8 @@ impl DeepLinkAction { crate::show_window(app.clone(), ShowCapWindow::Settings { page }).await } DeepLinkAction::PauseRecording => { + crate::recording::pause_recording(app.clone(), app.state()).await + } let state = app.state::>(); crate::recording::pause_recording(app.clone(), state).await } From d5c8eea08aae8062ebf481c81c4d67570b8eb74d Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 12:58:42 +0100 Subject: [PATCH 29/36] Update apps/desktop/src-tauri/src/deeplink_actions.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/deeplink_actions.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index befe07695d..2a398caf3f 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -194,6 +194,8 @@ impl DeepLinkAction { crate::recording::pause_recording(app.clone(), state).await } DeepLinkAction::ResumeRecording => { + crate::recording::resume_recording(app.clone(), app.state()).await + } let state = app.state::>(); crate::recording::resume_recording(app.clone(), state).await } From 6b37436aad15e82af5dd42371e2f45dc62670e6a Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 13:04:52 +0100 Subject: [PATCH 30/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index cec2e42c4e..22b336b016 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -661,7 +661,10 @@ pub async fn cycle_camera_input( }; let next_id = next_camera.map(|c| DeviceOrModelID::DeviceID(c.device_id().to_string())); - + let next_id = next_camera.map(|c| match c.model_id() { + Some(model_id) => DeviceOrModelID::ModelID(model_id.to_string()), + None => DeviceOrModelID::DeviceID(c.device_id().to_string()), + }); set_camera_input(app_handle, state, next_id).await } From 896b7912faae85b8fce362ef1ab295225a813f6a Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 13:05:20 +0100 Subject: [PATCH 31/36] Update apps/desktop/src-tauri/src/deeplink_actions.rs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/deeplink_actions.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index 2a398caf3f..d03be9994a 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -195,9 +195,6 @@ impl DeepLinkAction { } DeepLinkAction::ResumeRecording => { crate::recording::resume_recording(app.clone(), app.state()).await - } - let state = app.state::>(); - crate::recording::resume_recording(app.clone(), state).await } DeepLinkAction::SetMicrophone { label } => { let permissions = crate::permissions::do_permissions_check(false); From 1312514077737db6b7f7998ff41f4bde205c4f36 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 13:06:14 +0100 Subject: [PATCH 32/36] Update apps/desktop/src-tauri/src/deeplink_actions.rs Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/deeplink_actions.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index d03be9994a..1274ce1579 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -189,9 +189,6 @@ impl DeepLinkAction { } DeepLinkAction::PauseRecording => { crate::recording::pause_recording(app.clone(), app.state()).await - } - let state = app.state::>(); - crate::recording::pause_recording(app.clone(), state).await } DeepLinkAction::ResumeRecording => { crate::recording::resume_recording(app.clone(), app.state()).await From 02cd15ccc276010afbeaf4620f1df3a15afff803 Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 13:06:35 +0100 Subject: [PATCH 33/36] Update apps/desktop/src-tauri/src/deeplink_actions.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/deeplink_actions.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index 1274ce1579..327d5cba10 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -190,6 +190,11 @@ impl DeepLinkAction { 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 } From 4a1cc862457b0fec761d45cced137523cc9fe75e Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 13:26:11 +0100 Subject: [PATCH 34/36] Update apps/raycast-extension/README.md Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/raycast-extension/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/raycast-extension/README.md b/apps/raycast-extension/README.md index ba6292be42..91068f4bc5 100644 --- a/apps/raycast-extension/README.md +++ b/apps/raycast-extension/README.md @@ -14,6 +14,7 @@ Control the Cap desktop app from Raycast. ## Installation 1. `cd apps/raycast-extension` -2. `npm install` +2. `pnpm install` +3. `pnpm build` 3. `npm run build` 4. Import into Raycast via "Import Extension". From 93dc03d4c0ef31e144eb82e605c582f7e50c918f Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 13:27:41 +0100 Subject: [PATCH 35/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index 22b336b016..aa5f7ad740 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -633,6 +633,8 @@ pub async fn cycle_camera_input( state: MutableState<'_, App>, ) -> Result<(), String> { if !permissions::do_permissions_check(false).camera.permitted() { + return Err("Camera permission denied".to_string()); + } return Ok(()); } From 53c90259b72cbd05f4d679395b5cb7be6d8f46ee Mon Sep 17 00:00:00 2001 From: Excellencedev Date: Sat, 24 Jan 2026 13:34:15 +0100 Subject: [PATCH 36/36] Update apps/desktop/src-tauri/src/lib.rs Co-authored-by: tembo[bot] <208362400+tembo[bot]@users.noreply.github.com> --- apps/desktop/src-tauri/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs index aa5f7ad740..6b7f88deb2 100644 --- a/apps/desktop/src-tauri/src/lib.rs +++ b/apps/desktop/src-tauri/src/lib.rs @@ -635,7 +635,9 @@ pub async fn cycle_camera_input( if !permissions::do_permissions_check(false).camera.permitted() { return Err("Camera permission denied".to_string()); } - return Ok(()); + if !permissions::do_permissions_check(false).camera.permitted() { + return Err("Camera permission denied".to_string()); + } } let current_id = {