From da710304de310f289311400dd070893b9843bbde Mon Sep 17 00:00:00 2001 From: Angelbeats <89266469@qq.com> Date: Sat, 9 May 2026 03:59:16 +0800 Subject: [PATCH 1/9] feat: apply superior sync to deeplink actions Added new deep link actions for recording control and status retrieval. --- .../desktop/src-tauri/src/deeplink_actions.rs | 78 ++++++++++++++++++- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index a1170284877..0935d8755a5 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -3,7 +3,7 @@ use cap_recording::{ }; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; -use tauri::{AppHandle, Manager, Url}; +use tauri::{AppHandle, Manager, Url, Runtime}; use tracing::trace; use crate::{App, ArcLock, recording::StartRecordingInputs, windows::ShowCapWindow}; @@ -26,12 +26,27 @@ pub enum DeepLinkAction { mode: RecordingMode, }, StopRecording, + PauseRecording, + ResumeRecording, + TogglePauseRecording, + SwitchCamera { + camera_id: DeviceOrModelID, + }, + SwitchMic { + mic_label: String, + }, OpenEditor { project_path: PathBuf, }, OpenSettings { page: Option, }, + GetStatus, +} + +#[derive(Debug, Clone, Serialize)] +struct RecordingStatusPayload { + status: String, } pub fn handle(app_handle: &AppHandle, urls: Vec) { @@ -49,7 +64,6 @@ pub fn handle(app_handle: &AppHandle, urls: Vec) { ActionParseFromUrlError::Invalid => { eprintln!("Invalid deep link format \"{}\"", &url) } - // Likely login action, not handled here. ActionParseFromUrlError::NotAction => {} }) .ok() @@ -70,6 +84,12 @@ pub fn handle(app_handle: &AppHandle, urls: Vec) { }); } +fn emit_status_change(app: &AppHandle, status: &str) { + let _ = app.emit_all("cap://recording-status", RecordingStatusPayload { + status: status.to_string(), + }); +} + pub enum ActionParseFromUrlError { ParseFailed(String), Invalid, @@ -142,10 +162,60 @@ impl DeepLinkAction { crate::recording::start_recording(app.clone(), state, inputs) .await - .map(|_| ()) + .map(|_| { + emit_status_change(app, "recording"); + }) } DeepLinkAction::StopRecording => { - crate::recording::stop_recording(app.clone(), app.state()).await + crate::recording::stop_recording(app.clone(), app.state()).await.map(|_| { + emit_status_change(app, "idle"); + }) + } + DeepLinkAction::PauseRecording => { + let state = app.state::>(); + crate::recording::pause_recording(app.clone(), state).await.map(|_| { + emit_status_change(app, "paused"); + }) + } + DeepLinkAction::ResumeRecording => { + let state = app.state::>(); + crate::recording::resume_recording(app.clone(), state).await.map(|_| { + emit_status_change(app, "recording"); + }) + } + DeepLinkAction::TogglePauseRecording => { + let state = app.state::>(); + let app_state = state.read().unwrap(); + if app_state.recording_state.is_paused() { + drop(app_state); + crate::recording::resume_recording(app.clone(), state).await.map(|_| { + emit_status_change(app, "recording"); + }) + } else { + drop(app_state); + crate::recording::pause_recording(app.clone(), state).await.map(|_| { + emit_status_change(app, "paused"); + }) + } + } + DeepLinkAction::SwitchCamera { camera_id } => { + let state = app.state::>(); + crate::set_camera_input(app.clone(), state, Some(camera_id), None).await + } + DeepLinkAction::SwitchMic { mic_label } => { + let state = app.state::>(); + crate::set_mic_input(state, Some(mic_label)).await + } + DeepLinkAction::GetStatus => { + let state = app.state::>(); + let app_state = state.read().unwrap(); + let status = if app_state.recording_state.is_recording() { + if app_state.recording_state.is_paused() { "paused" } else { "recording" } + } else { + "idle" + }; + emit_status_change(app, status); + Ok(()) } DeepLinkAction::OpenEditor { project_path } => { crate::open_project_from_path(Path::new(&project_path), app.clone()) From ec7543c18d67991768c2755d576c3b0b7348d820 Mon Sep 17 00:00:00 2001 From: Angelbeats <89266469@qq.com> Date: Sat, 9 May 2026 04:01:36 +0800 Subject: [PATCH 2/9] Create package.json --- apps/raycast-extension/package.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/raycast-extension/package.json diff --git a/apps/raycast-extension/package.json b/apps/raycast-extension/package.json new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/apps/raycast-extension/package.json @@ -0,0 +1 @@ + From d6314feb3b3bd89c1f82e8290c6ee31896680b21 Mon Sep 17 00:00:00 2001 From: Angelbeats <89266469@qq.com> Date: Sat, 9 May 2026 04:02:03 +0800 Subject: [PATCH 3/9] Create index.tsx --- apps/raycast-extension/src/index.tsx | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/raycast-extension/src/index.tsx diff --git a/apps/raycast-extension/src/index.tsx b/apps/raycast-extension/src/index.tsx new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/apps/raycast-extension/src/index.tsx @@ -0,0 +1 @@ + From abbf4f920671c340034098137b4d958cc4fe6018 Mon Sep 17 00:00:00 2001 From: Angelbeats <89266469@qq.com> Date: Sat, 9 May 2026 07:57:14 +0800 Subject: [PATCH 4/9] Fix deeplink actions with async state locks and InProgressRecording API --- .../desktop/src-tauri/src/deeplink_actions.rs | 76 ++++++++++++------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/apps/desktop/src-tauri/src/deeplink_actions.rs b/apps/desktop/src-tauri/src/deeplink_actions.rs index 0935d8755a5..60900daac62 100644 --- a/apps/desktop/src-tauri/src/deeplink_actions.rs +++ b/apps/desktop/src-tauri/src/deeplink_actions.rs @@ -3,10 +3,11 @@ use cap_recording::{ }; use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; -use tauri::{AppHandle, Manager, Url, Runtime}; +use tauri::{AppHandle, Manager, Url, Runtime, Emitter}; use tracing::trace; -use crate::{App, ArcLock, recording::StartRecordingInputs, windows::ShowCapWindow}; +use crate::{App, ArcLock, recording::StartRecordingInputs, windows::ShowCapWindow, MutableState}; +use crate::recording::{InProgressRecording, RecordingEvent, RecordingState}; #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -41,7 +42,7 @@ pub enum DeepLinkAction { OpenSettings { page: Option, }, - GetStatus, + GetStatus, } #[derive(Debug, Clone, Serialize)] @@ -135,7 +136,7 @@ impl DeepLinkAction { capture_system_audio, mode, } => { - let state = app.state::>(); + let state: MutableState<'_, App> = app.state(); crate::set_camera_input(app.clone(), state.clone(), camera, None).await?; crate::set_mic_input(state.clone(), mic_label).await?; @@ -167,52 +168,69 @@ impl DeepLinkAction { }) } DeepLinkAction::StopRecording => { - crate::recording::stop_recording(app.clone(), app.state()).await.map(|_| { + let state: MutableState<'_, App> = app.state(); + crate::recording::stop_recording(app.clone(), state).await.map(|_| { emit_status_change(app, "idle"); }) } DeepLinkAction::PauseRecording => { - let state = app.state::>(); - crate::recording::pause_recording(app.clone(), state).await.map(|_| { + let state: MutableState<'_, App> = app.state(); + let mut state_guard = state.write().await; + if let RecordingState::Active(recording) = &mut state_guard.recording_state { + recording.pause().await.map_err(|e| e.to_string())?; + RecordingEvent::Paused.emit(app).ok(); emit_status_change(app, "paused"); - }) + } + Ok(()) } DeepLinkAction::ResumeRecording => { - let state = app.state::>(); - crate::recording::resume_recording(app.clone(), state).await.map(|_| { + let state: MutableState<'_, App> = app.state(); + let mut state_guard = state.write().await; + if let RecordingState::Active(recording) = &mut state_guard.recording_state { + recording.resume().await.map_err(|e| e.to_string())?; + RecordingEvent::Resumed.emit(app).ok(); emit_status_change(app, "recording"); - }) + } + Ok(()) } DeepLinkAction::TogglePauseRecording => { - let state = app.state::>(); - let app_state = state.read().unwrap(); - if app_state.recording_state.is_paused() { - drop(app_state); - crate::recording::resume_recording(app.clone(), state).await.map(|_| { + let state: MutableState<'_, App> = app.state(); + let mut state_guard = state.write().await; + if let RecordingState::Active(recording) = &mut state_guard.recording_state { + let is_paused = recording.is_paused().await.map_err(|e| e.to_string())?; + if is_paused { + recording.resume().await.map_err(|e| e.to_string())?; + RecordingEvent::Resumed.emit(app).ok(); emit_status_change(app, "recording"); - }) - } else { - drop(app_state); - crate::recording::pause_recording(app.clone(), state).await.map(|_| { + } else { + recording.pause().await.map_err(|e| e.to_string())?; + RecordingEvent::Paused.emit(app).ok(); emit_status_change(app, "paused"); - }) + } } + Ok(()) } DeepLinkAction::SwitchCamera { camera_id } => { - let state = app.state::>(); + let state: MutableState<'_, App> = app.state(); crate::set_camera_input(app.clone(), state, Some(camera_id), None).await } DeepLinkAction::SwitchMic { mic_label } => { - let state = app.state::>(); + let state: MutableState<'_, App> = app.state(); crate::set_mic_input(state, Some(mic_label)).await } DeepLinkAction::GetStatus => { - let state = app.state::>(); - let app_state = state.read().unwrap(); - let status = if app_state.recording_state.is_recording() { - if app_state.recording_state.is_paused() { "paused" } else { "recording" } - } else { - "idle" + let state: MutableState<'_, App> = app.state(); + let state_guard = state.read().await; + let status = match &state_guard.recording_state { + RecordingState::None => "idle", + RecordingState::Pending { .. } => "pending", + RecordingState::Active(recording) => { + if recording.is_paused().await.unwrap_or(false) { + "paused" + } else { + "recording" + } + } }; emit_status_change(app, status); Ok(()) From 5153c61ad5f71940d9b04d649271f53f0d41f087 Mon Sep 17 00:00:00 2001 From: Angelbeats <89266469@qq.com> Date: Sat, 9 May 2026 07:57:51 +0800 Subject: [PATCH 5/9] Update Raycast extension package.json --- apps/raycast-extension/package.json | 39 ++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/apps/raycast-extension/package.json b/apps/raycast-extension/package.json index 8b137891791..48f2a281f33 100644 --- a/apps/raycast-extension/package.json +++ b/apps/raycast-extension/package.json @@ -1 +1,38 @@ - +{ + "name": "cap-raycast", + "title": "Cap", + "description": "Control Cap recording via Raycast", + "icon": "command-icon.png", + "author": "Angelebeats", + "categories": [ + "Productivity" + ], + "license": "MIT", + "commands": [ + { + "name": "index", + "title": "Control Recording", + "description": "Start, stop, pause, or resume Cap recording", + "mode": "menu-bar" + } + ], + "dependencies": { + "@raycast/api": "^1.72.0", + "@raycast/utils": "^1.15.0" + }, + "devDependencies": { + "@raycast/eslint-config": "^1.0.8", + "@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", + "publish": "npx @raycast/api@latest publish" + } +} From 9e04d552a22adc677b07032da55402a6e072587a Mon Sep 17 00:00:00 2001 From: Angelbeats <89266469@qq.com> Date: Sat, 9 May 2026 07:58:24 +0800 Subject: [PATCH 6/9] Update Raycast extension index.tsx with recording logic --- apps/raycast-extension/src/index.tsx | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/apps/raycast-extension/src/index.tsx b/apps/raycast-extension/src/index.tsx index 8b137891791..fe39d634c16 100644 --- a/apps/raycast-extension/src/index.tsx +++ b/apps/raycast-extension/src/index.tsx @@ -1 +1,53 @@ +import { MenuBarExtra, open, showHUD } from "@raycast/api"; +import { useCachedState } from "@raycast/utils"; +import { useEffect } from "react"; +export default function Command() { + const [status, setStatus] = useCachedState<"idle" | "recording" | "paused">("recording-status", "idle"); + + const sendAction = async (action: string, value: object = {}) => { + const json = JSON.stringify({ [action]: value }); + const url = `cap://action?value=${encodeURIComponent(json)}`; + await open(url); + + // Optimistic update for immediate feedback + if (action === "stop_recording") setStatus("idle"); + if (action === "start_recording") setStatus("recording"); + if (action === "pause_recording") setStatus("paused"); + if (action === "resume_recording") setStatus("recording"); + }; + + return ( + + {status === "idle" ? ( + sendAction("start_recording", { + capture_mode: { screen: "Display 1" }, + mode: "video", + capture_system_audio: true + })} + /> + ) : ( + <> + sendAction("stop_recording")} + /> + sendAction(status === "paused" ? "resume_recording" : "pause_recording")} + /> + + )} + + sendAction("open_settings")} + /> + + ); +} From af6805f7234ce668ae034ec3881435689c0af24a Mon Sep 17 00:00:00 2001 From: Angelbeats <89266469@qq.com> Date: Sat, 9 May 2026 08:18:22 +0800 Subject: [PATCH 7/9] Update deeplink_actions.rs From 1d2dcb8840a05eed134bee085d7dc1950f652391 Mon Sep 17 00:00:00 2001 From: Angelbeats <89266469@qq.com> Date: Sat, 9 May 2026 08:19:10 +0800 Subject: [PATCH 8/9] Update apps/raycast-extension/package.json with correct metadata and commands From 53a0d312e51ffd1b475d38f724fa40a8e453899e Mon Sep 17 00:00:00 2001 From: Angelbeats <89266469@qq.com> Date: Sat, 9 May 2026 08:20:50 +0800 Subject: [PATCH 9/9] Update raycast-extension index.tsx for superior sync --- apps/raycast-extension/src/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/raycast-extension/src/index.tsx b/apps/raycast-extension/src/index.tsx index fe39d634c16..0cfb61b8f95 100644 --- a/apps/raycast-extension/src/index.tsx +++ b/apps/raycast-extension/src/index.tsx @@ -1,6 +1,5 @@ -import { MenuBarExtra, open, showHUD } from "@raycast/api"; +import { MenuBarExtra, open } from "@raycast/api"; import { useCachedState } from "@raycast/utils"; -import { useEffect } from "react"; export default function Command() { const [status, setStatus] = useCachedState<"idle" | "recording" | "paused">("recording-status", "idle");