Skip to content
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 73 additions & 4 deletions apps/desktop/src-tauri/src/deeplink_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use std::path::{Path, PathBuf};
use tauri::{AppHandle, Manager, Url};
use tracing::trace;

use crate::{App, ArcLock, recording::StartRecordingInputs, windows::ShowCapWindow};
use crate::{
recording::StartRecordingInputs, recording_settings::RecordingSettingsStore,
windows::ShowCapWindow, App, ArcLock,
};

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
Expand All @@ -25,7 +28,12 @@ pub enum DeepLinkAction {
capture_system_audio: bool,
mode: RecordingMode,
},
Record,
StopRecording,
Pause,
Resume,
ToggleMic,
ToggleCam,
OpenEditor {
project_path: PathBuf,
},
Expand Down Expand Up @@ -88,9 +96,15 @@ impl TryFrom<&Url> for DeepLinkAction {
}

match url.domain() {
Some(v) if v != "action" => Err(ActionParseFromUrlError::NotAction),
_ => Err(ActionParseFromUrlError::Invalid),
}?;
Some("record") => return Ok(Self::Record),
Some("stop") => return Ok(Self::StopRecording),
Some("pause") => return Ok(Self::Pause),
Some("resume") => return Ok(Self::Resume),
Some("toggle-mic") => return Ok(Self::ToggleMic),
Some("toggle-cam") => return Ok(Self::ToggleCam),
Some(v) if v != "action" => return Err(ActionParseFromUrlError::NotAction),
_ => {}
}
Comment on lines 98 to +108
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Right now a hostless URL (e.g. cap:?value=...) will fall through and be treated as an action. If you only want the JSON value payload to work for the action host, you can make that explicit.

Suggested change
match url.domain() {
Some(v) if v != "action" => Err(ActionParseFromUrlError::NotAction),
_ => Err(ActionParseFromUrlError::Invalid),
}?;
Some("record") => return Ok(Self::Record),
Some("stop") => return Ok(Self::StopRecording),
Some("pause") => return Ok(Self::Pause),
Some("resume") => return Ok(Self::Resume),
Some("toggle-mic") => return Ok(Self::ToggleMic),
Some("toggle-cam") => return Ok(Self::ToggleCam),
Some(v) if v != "action" => return Err(ActionParseFromUrlError::NotAction),
_ => {}
}
match url.domain() {
Some("record") => return Ok(Self::Record),
Some("stop") => return Ok(Self::StopRecording),
Some("pause") => return Ok(Self::Pause),
Some("resume") => return Ok(Self::Resume),
Some("toggle-mic") => return Ok(Self::ToggleMic),
Some("toggle-cam") => return Ok(Self::ToggleCam),
Some(v) if v != "action" => return Err(ActionParseFromUrlError::NotAction),
Some("action") => {}
None => return Err(ActionParseFromUrlError::Invalid),
}


let params = url
.query_pairs()
Expand Down Expand Up @@ -143,9 +157,64 @@ impl DeepLinkAction {
.await
.map(|_| ())
}
DeepLinkAction::Record => {
let settings = RecordingSettingsStore::get(app)?.unwrap_or_default();
let capture_target = settings.target.ok_or("No capture target set in settings")?;
let mode = settings.mode.unwrap_or(RecordingMode::Instant);

let state = app.state::<ArcLock<App>>();
crate::set_camera_input(app.clone(), state.clone(), settings.camera_id, None)
.await?;
crate::set_mic_input(state.clone(), settings.mic_name).await?;

let inputs = StartRecordingInputs {
mode,
capture_target,
capture_system_audio: settings.system_audio,
organization_id: settings.organization_id,
};

crate::recording::start_recording(app.clone(), state, inputs)
.await
.map(|_| ())
}
Comment on lines +212 to +226
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

duplicate logic with StartRecording handler (lines 124-159)

both Record and StartRecording handlers perform identical operations: get settings, set camera/mic inputs, create StartRecordingInputs, and call start_recording. The only difference is where the configuration comes from (settings store vs deep link parameters)

Suggested change
DeepLinkAction::Record => {
let settings = RecordingSettingsStore::get(app)?.unwrap_or_default();
let capture_target = settings.target.ok_or("No capture target set in settings")?;
let mode = settings.mode.unwrap_or(RecordingMode::Instant);
let state = app.state::<ArcLock<App>>();
crate::set_camera_input(app.clone(), state.clone(), settings.camera_id, None)
.await?;
crate::set_mic_input(state.clone(), settings.mic_name).await?;
let inputs = StartRecordingInputs {
mode,
capture_target,
capture_system_audio: settings.system_audio,
organization_id: settings.organization_id,
};
crate::recording::start_recording(app.clone(), state, inputs)
.await
.map(|_| ())
}
DeepLinkAction::Record => {
let settings = RecordingSettingsStore::get(app)?.unwrap_or_default();
let capture_target = settings.target.ok_or("No capture target set in settings")?;
Self::StartRecording {
capture_mode: match capture_target {
ScreenCaptureTarget::Display { id } => CaptureMode::Screen(
cap_recording::screen_capture::list_displays()
.into_iter()
.find(|(s, _)| s.id == id)
.map(|(s, _)| s.name)
.ok_or("Display not found")?
),
ScreenCaptureTarget::Window { id } => CaptureMode::Window(
cap_recording::screen_capture::list_windows()
.into_iter()
.find(|(w, _)| w.id == id)
.map(|(w, _)| w.name)
.ok_or("Window not found")?
),
},
camera: settings.camera_id,
mic_label: settings.mic_name,
capture_system_audio: settings.system_audio,
mode: settings.mode.unwrap_or(RecordingMode::Instant),
}.execute(app).await
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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

Comment:
duplicate logic with `StartRecording` handler (lines 124-159)

both `Record` and `StartRecording` handlers perform identical operations: get settings, set camera/mic inputs, create `StartRecordingInputs`, and call `start_recording`. The only difference is where the configuration comes from (settings store vs deep link parameters)

```suggestion
            DeepLinkAction::Record => {
                let settings = RecordingSettingsStore::get(app)?.unwrap_or_default();
                let capture_target = settings.target.ok_or("No capture target set in settings")?;
                
                Self::StartRecording {
                    capture_mode: match capture_target {
                        ScreenCaptureTarget::Display { id } => CaptureMode::Screen(
                            cap_recording::screen_capture::list_displays()
                                .into_iter()
                                .find(|(s, _)| s.id == id)
                                .map(|(s, _)| s.name)
                                .ok_or("Display not found")?
                        ),
                        ScreenCaptureTarget::Window { id } => CaptureMode::Window(
                            cap_recording::screen_capture::list_windows()
                                .into_iter()
                                .find(|(w, _)| w.id == id)
                                .map(|(w, _)| w.name)
                                .ok_or("Window not found")?
                        ),
                    },
                    camera: settings.camera_id,
                    mic_label: settings.mic_name,
                    capture_system_audio: settings.system_audio,
                    mode: settings.mode.unwrap_or(RecordingMode::Instant),
                }.execute(app).await
            }
```

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

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

DeepLinkAction::StopRecording => {
crate::recording::stop_recording(app.clone(), app.state()).await
}
DeepLinkAction::Pause => {
crate::recording::pause_recording(app.clone(), app.state()).await
}
DeepLinkAction::Resume => {
crate::recording::resume_recording(app.clone(), app.state()).await
}
DeepLinkAction::ToggleMic => {
let state = app.state::<ArcLock<App>>();
let current_mic = {
let app_state = state.read().await;
app_state.selected_mic_label.clone()
};

if current_mic.is_some() {
crate::set_mic_input(state, None).await
} else {
let settings = RecordingSettingsStore::get(app)?.unwrap_or_default();
crate::set_mic_input(state, settings.mic_name).await
}
Comment on lines +245 to +251
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In the toggle-on path, if settings.mic_name is None this ends up calling set_mic_input(..., None) (no-op) but still reports success. Might be nicer to surface a clear error for the caller.

Suggested change
} else {
let settings = RecordingSettingsStore::get(app)?.unwrap_or_default();
crate::set_mic_input(state, settings.mic_name).await
}
} else {
let settings = RecordingSettingsStore::get(app)?.unwrap_or_default();
let mic_name = settings.mic_name.ok_or("No mic selected in settings")?;
crate::set_mic_input(state, Some(mic_name)).await
}

}
DeepLinkAction::ToggleCam => {
let state = app.state::<ArcLock<App>>();
let camera_in_use = {
let app_state = state.read().await;
app_state.camera_in_use
};

if camera_in_use {
crate::set_camera_input(app.clone(), state, None, None).await
} else {
let settings = RecordingSettingsStore::get(app)?.unwrap_or_default();
crate::set_camera_input(app.clone(), state, settings.camera_id, None).await
}
Comment on lines +262 to +268
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Same idea for camera toggle: if settings.camera_id is None, toggling on becomes a no-op. Returning a concrete error can make Raycast (or other clients) behave better.

Suggested change
} else {
let settings = RecordingSettingsStore::get(app)?.unwrap_or_default();
crate::set_camera_input(app.clone(), state, settings.camera_id, None).await
}
} else {
let settings = RecordingSettingsStore::get(app)?.unwrap_or_default();
let camera_id = settings.camera_id.ok_or("No camera selected in settings")?;
crate::set_camera_input(app.clone(), state, Some(camera_id), None).await
}

}
DeepLinkAction::OpenEditor { project_path } => {
crate::open_project_from_path(Path::new(&project_path), app.clone())
}
Expand Down