Skip to content
9 changes: 6 additions & 3 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::{
App, ArcLock, apply_camera_input, apply_mic_input, recording::StartRecordingInputs,
windows::ShowCapWindow,
};

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
Expand Down Expand Up @@ -116,8 +119,8 @@ impl DeepLinkAction {
} => {
let state = app.state::<ArcLock<App>>();

crate::set_camera_input(app.clone(), state.clone(), camera).await?;
crate::set_mic_input(state.clone(), mic_label).await?;
apply_camera_input(app.clone(), state.clone(), camera).await?;
apply_mic_input(state.clone(), mic_label).await?;

let capture_target: ScreenCaptureTarget = match capture_mode {
CaptureMode::Screen(name) => cap_recording::screen_capture::list_displays()
Expand Down
19 changes: 17 additions & 2 deletions apps/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,13 @@ impl App {
#[specta::specta]
#[instrument(skip(state))]
async fn set_mic_input(state: MutableState<'_, App>, label: Option<String>) -> Result<(), String> {
apply_mic_input(state, label).await
}

pub(crate) async fn apply_mic_input(
state: MutableState<'_, App>,
label: Option<String>,
) -> Result<(), String> {
let (mic_feed, studio_handle, current_label) = {
let app = state.read().await;
let handle = match app.current_recording() {
Expand Down Expand Up @@ -414,6 +421,14 @@ async fn set_camera_input(
app_handle: AppHandle,
state: MutableState<'_, App>,
id: Option<DeviceOrModelID>,
) -> Result<(), String> {
apply_camera_input(app_handle, state, id).await
}

pub(crate) async fn apply_camera_input(
app_handle: AppHandle,
state: MutableState<'_, App>,
id: Option<DeviceOrModelID>,
) -> Result<(), String> {
let app = state.read().await;
let camera_feed = app.camera_feed.clone();
Expand Down Expand Up @@ -2543,8 +2558,8 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
.flatten()
.unwrap_or_default();

let _ = set_mic_input(app.state(), settings.mic_name).await;
let _ = set_camera_input(app.clone(), app.state(), settings.camera_id).await;
let _ = apply_mic_input(app.state(), settings.mic_name).await;
let _ = apply_camera_input(app.clone(), app.state(), settings.camera_id).await;

let _ = start_recording(app.clone(), app.state(), {
recording::StartRecordingInputs {
Expand Down
55 changes: 55 additions & 0 deletions apps/desktop/src-tauri/src/recording.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
App, CurrentRecordingChanged, MutableState, NewStudioRecordingAdded, RecordingState,
RecordingStopped, VideoUploadInfo,
api::PresignedS3PutRequestMethod,
apply_camera_input, apply_mic_input,
audio::AppSounds,
auth::AuthStore,
create_screenshot,
Expand All @@ -55,6 +56,7 @@
},
open_external_link,
presets::PresetsStore,
recording_settings::RecordingSettingsStore,
thumbnails::*,
upload::{
InstantMultipartUpload, build_video_meta, compress_image, create_or_get_video, upload_video,
Expand Down Expand Up @@ -349,6 +351,41 @@
UpgradeRequired,
}

async fn restore_inputs_from_store_if_missing(app: &AppHandle, state: &MutableState<'_, App>) {
let guard = state.read().await;
let recording_active = !matches!(guard.recording_state, RecordingState::None);
let needs_mic = guard.selected_mic_label.is_none();
let needs_camera = guard.selected_camera_id.is_none();
drop(guard);

if recording_active || (!needs_mic && !needs_camera) {
return;
}

let settings = match RecordingSettingsStore::get(app) {
Ok(Some(settings)) => settings,
Ok(None) => return,
Err(err) => {
warn!(%err, "Failed to load recording settings while restoring inputs");
return;
}
};

if let Some(mic) = settings.mic_name.clone().filter(|_| needs_mic) {
match apply_mic_input(app.state(), Some(mic)).await {
Err(err) => warn!(%err, "Failed to restore microphone input"),
Ok(_) => {}
}

Check warning on line 378 in apps/desktop/src-tauri/src/recording.rs

View workflow job for this annotation

GitHub Actions / Clippy (aarch64-apple-darwin, macos-latest)

you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`

warning: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` --> apps/desktop/src-tauri/src/recording.rs:375:9 | 375 | / match apply_mic_input(app.state(), Some(mic)).await { 376 | | Err(err) => warn!(%err, "Failed to restore microphone input"), 377 | | Ok(_) => {} 378 | | } | |_________^ help: try: `if let Err(err) = apply_mic_input(app.state(), Some(mic)).await { warn!(%err, "Failed to restore microphone input") }` | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#single_match = note: `#[warn(clippy::single_match)]` on by default
}

if let Some(camera) = settings.camera_id.clone().filter(|_| needs_camera) {
match apply_camera_input(app.clone(), app.state(), Some(camera)).await {
Err(err) => warn!(%err, "Failed to restore camera input"),
Ok(_) => {}
}

Check warning on line 385 in apps/desktop/src-tauri/src/recording.rs

View workflow job for this annotation

GitHub Actions / Clippy (aarch64-apple-darwin, macos-latest)

you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`

warning: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` --> apps/desktop/src-tauri/src/recording.rs:382:9 | 382 | / match apply_camera_input(app.clone(), app.state(), Some(camera)).await { 383 | | Err(err) => warn!(%err, "Failed to restore camera input"), 384 | | Ok(_) => {} 385 | | } | |_________^ help: try: `if let Err(err) = apply_camera_input(app.clone(), app.state(), Some(camera)).await { warn!(%err, "Failed to restore camera input") }` | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#single_match
}
}

#[tauri::command]
#[specta::specta]
#[tracing::instrument(name = "recording", skip_all)]
Expand All @@ -357,10 +394,28 @@
state_mtx: MutableState<'_, App>,
inputs: StartRecordingInputs,
) -> Result<RecordingAction, String> {
restore_inputs_from_store_if_missing(&app, &state_mtx).await;

if !matches!(state_mtx.read().await.recording_state, RecordingState::None) {
return Err("Recording already in progress".to_string());
}

let has_camera_selected = {
let guard = state_mtx.read().await;
guard.selected_camera_id.is_some()
};
let camera_window_open = CapWindowId::Camera.get(&app).is_some();
let should_open_camera_preview =
matches!(inputs.mode, RecordingMode::Instant) && has_camera_selected && !camera_window_open;

if should_open_camera_preview {
ShowCapWindow::Camera
.show(&app)
.await
.map_err(|err| error!("Failed to show camera preview window: {err}"))
.ok();
}

let id = uuid::Uuid::new_v4().to_string();
let general_settings = GeneralSettingsStore::get(&app).ok().flatten();
let general_settings = general_settings.as_ref();
Expand Down
48 changes: 46 additions & 2 deletions apps/desktop/src-tauri/src/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
use tracing::{debug, error, instrument, warn};

use crate::{
App, ArcLock, RequestScreenCapturePrewarm, fake_window,
App, ArcLock, RequestScreenCapturePrewarm, apply_camera_input, apply_mic_input, fake_window,
general_settings::{self, AppTheme, GeneralSettingsStore},
permissions,
recording_settings::RecordingTargetMode,
recording_settings::{RecordingSettingsStore, RecordingTargetMode},
target_select_overlay::WindowFocusManager,
window_exclusion::WindowExclusion,
};
Expand Down Expand Up @@ -282,6 +282,8 @@
crate::platform::set_window_level(window.as_ref().window(), 50);
}

restore_recording_inputs_if_idle(app);

#[cfg(target_os = "macos")]
{
let app_handle = app.clone();
Expand Down Expand Up @@ -797,6 +799,48 @@
}
}

fn restore_recording_inputs_if_idle(app: &AppHandle<Wry>) {
let settings = match RecordingSettingsStore::get(app) {
Ok(Some(settings)) => settings,
Ok(None) => return,
Err(err) => {
warn!(%err, "Failed to load recording settings while restoring inputs");
return;
}
};

let mic_name = settings.mic_name.clone();
let camera_id = settings.camera_id.clone();

if mic_name.is_none() && camera_id.is_none() {
return;
}

let app_handle = app.clone();
let state = app_handle.state::<ArcLock<App>>();
let app_state = state.inner().clone();

tauri::async_runtime::spawn(async move {
if app_state.read().await.is_recording_active_or_pending() {
return;
}

if let Some(mic) = mic_name {
match apply_mic_input(app_handle.state(), Some(mic)).await {
Err(err) => warn!(%err, "Failed to restore microphone input"),
Ok(_) => {}
}

Check warning on line 832 in apps/desktop/src-tauri/src/windows.rs

View workflow job for this annotation

GitHub Actions / Clippy (aarch64-apple-darwin, macos-latest)

you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`

warning: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` --> apps/desktop/src-tauri/src/windows.rs:829:13 | 829 | / match apply_mic_input(app_handle.state(), Some(mic)).await { 830 | | Err(err) => warn!(%err, "Failed to restore microphone input"), 831 | | Ok(_) => {} 832 | | } | |_____________^ help: try: `if let Err(err) = apply_mic_input(app_handle.state(), Some(mic)).await { warn!(%err, "Failed to restore microphone input") }` | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#single_match
}

if let Some(camera) = camera_id {
match apply_camera_input(app_handle.clone(), app_handle.state(), Some(camera)).await {
Err(err) => warn!(%err, "Failed to restore camera input"),
Ok(_) => {}
}

Check warning on line 839 in apps/desktop/src-tauri/src/windows.rs

View workflow job for this annotation

GitHub Actions / Clippy (aarch64-apple-darwin, macos-latest)

you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`

warning: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` --> apps/desktop/src-tauri/src/windows.rs:836:13 | 836 | / match apply_camera_input(app_handle.clone(), app_handle.state(), Some(camera)).await { 837 | | Err(err) => warn!(%err, "Failed to restore camera input"), 838 | | Ok(_) => {} 839 | | } | |_____________^ help: try: `if let Err(err) = apply_camera_input(app_handle.clone(), app_handle.state(), Some(camera)).await { warn!(%err, "Failed to restore camera input") }` | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#single_match
}
});
}

#[cfg(target_os = "macos")]
fn add_traffic_lights(window: &WebviewWindow<Wry>, controls_inset: Option<LogicalPosition<f64>>) {
use crate::platform::delegates;
Expand Down
9 changes: 2 additions & 7 deletions apps/desktop/src/routes/camera.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
import { createStore } from "solid-js/store";
import { generalSettingsStore } from "~/store";
import { createTauriEventListener } from "~/utils/createEventListener";
import { createCameraMutation } from "~/utils/queries";
import { createImageDataWS, createLazySignal } from "~/utils/socket";
import { commands, events } from "~/utils/tauri";
import {
Expand Down Expand Up @@ -88,8 +87,6 @@ function NativeCameraPreviewPage(props: { disconnected: Accessor<boolean> }) {
commands.awaitCameraPreviewReady(),
);

const setCamera = createCameraMutation();

return (
<div
data-tauri-drag-region
Expand All @@ -101,7 +98,7 @@ function NativeCameraPreviewPage(props: { disconnected: Accessor<boolean> }) {
<div class="h-13">
<div class="flex flex-row justify-center items-center">
<div class="flex flex-row gap-[0.25rem] p-[0.25rem] opacity-0 group-hover:opacity-100 translate-y-2 group-hover:translate-y-0 rounded-xl transition-[opacity,transform] bg-gray-1 border border-white-transparent-20 text-gray-10">
<ControlButton onClick={() => setCamera.mutate(null)}>
<ControlButton onClick={() => void getCurrentWindow().close()}>
<IconCapCircleX class="size-5.5" />
</ControlButton>
<ControlButton
Expand Down Expand Up @@ -268,8 +265,6 @@ function LegacyCameraPreviewPage(props: { disconnected: Accessor<boolean> }) {

let cameraCanvasRef: HTMLCanvasElement | undefined;

const setCamera = createCameraMutation();

createEffect(
on(
() => rawOptions.cameraLabel,
Expand All @@ -294,7 +289,7 @@ function LegacyCameraPreviewPage(props: { disconnected: Accessor<boolean> }) {
<div class="h-14">
<div class="flex flex-row justify-center items-center">
<div class="flex flex-row gap-[0.25rem] p-[0.25rem] opacity-0 group-hover:opacity-100 translate-y-2 group-hover:translate-y-0 rounded-xl transition-[opacity,transform] bg-gray-1 border border-white-transparent-20 text-gray-10">
<ControlButton onClick={() => setCamera.mutate(null)}>
<ControlButton onClick={() => void getCurrentWindow().close()}>
<IconCapCircleX class="size-5.5" />
</ControlButton>
<ControlButton
Expand Down
Loading
Loading