-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: Deeplinks Support + Raycast Extension #1548
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ad896bf
a8c5a6f
540922e
c50550f
c3f0cf7
5c793f9
0c7fa42
4036ae5
5380812
f79d56a
75d77e4
8e6beb1
e0c15f1
3ea5202
752bfa9
c1c6a46
d4760c3
4f1d6a8
3ad5663
7360964
d44c8c3
6f335ea
9a7d1f4
1371856
eeb2da1
8a6c82e
12903b9
31af095
d5c8eea
6b37436
896b791
1312514
02cd15c
4a1cc86
93dc03d
53c9025
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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>) { | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -143,6 +154,30 @@ impl DeepLinkAction { | |||||||||||||||||||||||||||||||||||||
| .await | ||||||||||||||||||||||||||||||||||||||
| .map(|_| ()) | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| DeepLinkAction::StartDefaultRecording => { | ||||||||||||||||||||||||||||||||||||||
| 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::<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 | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -152,6 +187,43 @@ impl DeepLinkAction { | |||||||||||||||||||||||||||||||||||||
| DeepLinkAction::OpenSettings { page } => { | ||||||||||||||||||||||||||||||||||||||
| crate::show_window(app.clone(), ShowCapWindow::Settings { page }).await | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| DeepLinkAction::PauseRecording => { | ||||||||||||||||||||||||||||||||||||||
Excellencedev marked this conversation as resolved.
Show resolved
Hide resolved
Excellencedev marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||
| crate::recording::pause_recording(app.clone(), app.state()).await | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| DeepLinkAction::ResumeRecording => { | ||||||||||||||||||||||||||||||||||||||
Excellencedev marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+190
to
+200
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. syntax: duplicate match arms cause compilation error. Lines 196-200 repeat the
Suggested change
Prompt To Fix With AIThis 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 permissions = crate::permissions::do_permissions_check(false); | ||||||||||||||||||||||||||||||||||||||
| if !permissions.microphone.permitted() { | ||||||||||||||||||||||||||||||||||||||
| return Err("Microphone permission denied".to_string()); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| let state = app.state::<ArcLock<App>>(); | ||||||||||||||||||||||||||||||||||||||
| 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::<ArcLock<App>>(); | ||||||||||||||||||||||||||||||||||||||
| crate::set_camera_input(app.clone(), state, id).await | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| DeepLinkAction::CycleMicrophone => { | ||||||||||||||||||||||||||||||||||||||
| let state = app.state::<ArcLock<App>>(); | ||||||||||||||||||||||||||||||||||||||
| crate::cycle_mic_input(state).await | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+219
to
+222
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: missing permissions check before cycling microphone. Other microphone operations (like Prompt To Fix With AIThis 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: missing permissions check before cycling camera. Other camera operations (like Prompt To Fix With AIThis 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. |
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -588,6 +588,90 @@ async fn set_camera_input( | |||||||||||||||||||||||||
| Ok(()) | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| #[tauri::command] | ||||||||||||||||||||||||||
| #[specta::specta] | ||||||||||||||||||||||||||
| #[instrument(skip(state))] | ||||||||||||||||||||||||||
| pub async fn cycle_mic_input(state: MutableState<'_, App>) -> Result<(), String> { | ||||||||||||||||||||||||||
Excellencedev marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+591
to
+594
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/desktop/src-tauri/src/lib.rs
Line: 591:594
Comment:
**logic:** `cycle_mic_input` not registered in `tauri_specta::collect_commands!` around line 2673. Add it after `set_camera_input` on line 2675 so it's callable from deeplinks.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this is annotated as a |
||||||||||||||||||||||||||
| if !permissions::do_permissions_check(false) | ||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If this is used via deeplink/Raycast, returning
Suggested change
|
||||||||||||||||||||||||||
| .microphone | ||||||||||||||||||||||||||
| .permitted() | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| return Ok(()); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let current_label = { | ||||||||||||||||||||||||||
| let app = state.read().await; | ||||||||||||||||||||||||||
Excellencedev marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
| app.selected_mic_label.clone() | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let mut mic_list = MicrophoneFeed::list().keys().cloned().collect::<Vec<_>>(); | ||||||||||||||||||||||||||
| mic_list.sort_unstable(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Prompt To Fix With AIThis 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> { | ||||||||||||||||||||||||||
| if !permissions::do_permissions_check(false).camera.permitted() { | ||||||||||||||||||||||||||
Excellencedev marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
| return Err("Camera permission denied".to_string()); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| if !permissions::do_permissions_check(false).camera.permitted() { | ||||||||||||||||||||||||||
| return Err("Camera permission denied".to_string()); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+635
to
+641
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. syntax: duplicate permission check and orphaned closing brace cause compilation error. Lines 638-641 repeat the check and have an extra
Suggested change
Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/desktop/src-tauri/src/lib.rs
Line: 635:641
Comment:
**syntax:** duplicate permission check and orphaned closing brace cause compilation error. Lines 638-641 repeat the check and have an extra `}`.
```suggestion
if !permissions::do_permissions_check(false).camera.permitted() {
return Err("Camera permission denied".to_string());
}
let current_id = {
```
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let current_id = { | ||||||||||||||||||||||||||
| let app = state.read().await; | ||||||||||||||||||||||||||
Excellencedev marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||
| app.selected_camera_id.clone() | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let camera_list = cap_camera::list_cameras().collect::<Vec<_>>(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if camera_list.is_empty() { | ||||||||||||||||||||||||||
| return Ok(()); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| 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().is_some_and(|m| m == mod_id), | ||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||
| None => None, | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let next_camera = match current_index { | ||||||||||||||||||||||||||
| Some(i) => camera_list.get((i + 1) % camera_list.len()), | ||||||||||||||||||||||||||
| None => camera_list.first(), | ||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| let next_id = next_camera.map(|c| DeviceOrModelID::DeviceID(c.device_id().to_string())); | ||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: using
Suggested change
|
||||||||||||||||||||||||||
| 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()), | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
|
Comment on lines
+667
to
+671
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. syntax:
Suggested change
Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/desktop/src-tauri/src/lib.rs
Line: 667:671
Comment:
**syntax:** `next_id` is computed twice with different logic - the second assignment overwrites the first. The second one (lines 668-671) is the correct implementation as it prefers ModelID.
```suggestion
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()),
});
```
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||||||||||||||
| 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>>(); | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,20 @@ | ||||||||
| # 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. `pnpm install` | ||||||||
| 3. `pnpm build` | ||||||||
| 3. `npm run build` | ||||||||
|
Comment on lines
+18
to
+19
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. syntax: duplicate build step with conflicting commands. Line 18 says
Suggested change
Prompt To Fix With AIThis 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". | ||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,73 @@ | ||||||||||||||
| { | ||||||||||||||
| "$schema": "https://www.raycast.com/schemas/extension.json", | ||||||||||||||
| "name": "cap-control", | ||||||||||||||
Excellencedev marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||
| "version": "0.0.0", | ||||||||||||||
| "private": true, | ||||||||||||||
| "title": "Cap Control", | ||||||||||||||
| "description": "Control Cap desktop app", | ||||||||||||||
| "icon": "command-icon.png", | ||||||||||||||
| "author": "CapSoftware", | ||||||||||||||
| "categories": [ | ||||||||||||||
| "Productivity", | ||||||||||||||
| "Media" | ||||||||||||||
| ], | ||||||||||||||
| "license": "MIT", | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This repo isn't MIT-licensed overall (see root
Suggested change
|
||||||||||||||
| "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" | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| 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"); | ||
| } |
| 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"); | ||
| } |
| 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"); | ||
| } |
| 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"); | ||
| } |
| 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..."); | ||
| } |
| 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..."); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { closeMainWindow, open, showHUD } from "@raycast/api"; | ||
|
|
||
| type CapAction = string | Record<string, unknown>; | ||
|
|
||
| export async function sendCapCommand(action: CapAction, hudMessage: string): Promise<void> { | ||
| 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"); | ||
| } | ||
| } |
| 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/**/*" | ||
| ] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor security/UX thought:
StartDefaultRecordingis easy to trigger from outside the app (websites can open custom schemes). Might be worth gating recording-control deeplinks behind an opt-in setting or a lightweight confirmation when the trigger isn’t clearly user-initiated (e.g. Raycast).