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
57 changes: 57 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,43 @@
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 needs_mic {
if let Some(mic) = settings.mic_name.clone() {
if let Err(err) = apply_mic_input(app.state(), Some(mic)).await {
warn!(%err, "Failed to restore microphone input");
}
}

Check failure on line 379 in apps/desktop/src-tauri/src/recording.rs

View workflow job for this annotation

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

this `if` statement can be collapsed

error: this `if` statement can be collapsed --> apps/desktop/src-tauri/src/recording.rs:375:9 | 375 | / if let Some(mic) = settings.mic_name.clone() { 376 | | if let Err(err) = apply_mic_input(app.state(), Some(mic)).await { 377 | | warn!(%err, "Failed to restore microphone input"); 378 | | } 379 | | } | |_________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#collapsible_if help: collapse nested if block | 375 ~ if let Some(mic) = settings.mic_name.clone() 376 ~ && let Err(err) = apply_mic_input(app.state(), Some(mic)).await { 377 | warn!(%err, "Failed to restore microphone input"); 378 ~ } |
}

Check failure on line 380 in apps/desktop/src-tauri/src/recording.rs

View workflow job for this annotation

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

this `if` statement can be collapsed

error: this `if` statement can be collapsed --> apps/desktop/src-tauri/src/recording.rs:374:5 | 374 | / if needs_mic { 375 | | if let Some(mic) = settings.mic_name.clone() { 376 | | if let Err(err) = apply_mic_input(app.state(), Some(mic)).await { 377 | | warn!(%err, "Failed to restore microphone input"); ... | 380 | | } | |_____^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#collapsible_if = note: requested on the command line with `-D clippy::collapsible-if` help: collapse nested if block | 374 ~ if needs_mic 375 ~ && let Some(mic) = settings.mic_name.clone() { 376 | if let Err(err) = apply_mic_input(app.state(), Some(mic)).await { 377 | warn!(%err, "Failed to restore microphone input"); 378 | } 379 ~ } |

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

Check failure on line 387 in apps/desktop/src-tauri/src/recording.rs

View workflow job for this annotation

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

this `if` statement can be collapsed

error: this `if` statement can be collapsed --> apps/desktop/src-tauri/src/recording.rs:383:9 | 383 | / if let Some(camera) = settings.camera_id.clone() { 384 | | if let Err(err) = apply_camera_input(app.clone(), app.state(), Some(camera)).await { 385 | | warn!(%err, "Failed to restore camera input"); 386 | | } 387 | | } | |_________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#collapsible_if help: collapse nested if block | 383 ~ if let Some(camera) = settings.camera_id.clone() 384 ~ && let Err(err) = apply_camera_input(app.clone(), app.state(), Some(camera)).await { 385 | warn!(%err, "Failed to restore camera input"); 386 ~ } |
}

Check failure on line 388 in apps/desktop/src-tauri/src/recording.rs

View workflow job for this annotation

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

this `if` statement can be collapsed

error: this `if` statement can be collapsed --> apps/desktop/src-tauri/src/recording.rs:382:5 | 382 | / if needs_camera { 383 | | if let Some(camera) = settings.camera_id.clone() { 384 | | if let Err(err) = apply_camera_input(app.clone(), app.state(), Some(camera)).await { 385 | | warn!(%err, "Failed to restore camera input"); ... | 388 | | } | |_____^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#collapsible_if help: collapse nested if block | 382 ~ if needs_camera 383 ~ && let Some(camera) = settings.camera_id.clone() { 384 | if let Err(err) = apply_camera_input(app.clone(), app.state(), Some(camera)).await { 385 | warn!(%err, "Failed to restore camera input"); 386 | } 387 ~ } |
}

#[tauri::command]
#[specta::specta]
#[tracing::instrument(name = "recording", skip_all)]
Expand All @@ -357,10 +396,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 {
if let Err(err) = apply_mic_input(app_handle.state(), Some(mic)).await {
warn!(%err, "Failed to restore microphone input");
}
}

Check failure 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)

this `if` statement can be collapsed

error: this `if` statement can be collapsed --> apps/desktop/src-tauri/src/windows.rs:828:9 | 828 | / if let Some(mic) = mic_name { 829 | | if let Err(err) = apply_mic_input(app_handle.state(), Some(mic)).await { 830 | | warn!(%err, "Failed to restore microphone input"); 831 | | } 832 | | } | |_________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#collapsible_if help: collapse nested if block | 828 ~ if let Some(mic) = mic_name 829 ~ && let Err(err) = apply_mic_input(app_handle.state(), Some(mic)).await { 830 | warn!(%err, "Failed to restore microphone input"); 831 ~ } |

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

Check failure on line 840 in apps/desktop/src-tauri/src/windows.rs

View workflow job for this annotation

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

this `if` statement can be collapsed

error: this `if` statement can be collapsed --> apps/desktop/src-tauri/src/windows.rs:834:9 | 834 | / if let Some(camera) = camera_id { 835 | | if let Err(err) = 836 | | apply_camera_input(app_handle.clone(), app_handle.state(), Some(camera)).await ... | 840 | | } | |_________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/rust-1.91.0/index.html#collapsible_if help: collapse nested if block | 834 ~ if let Some(camera) = camera_id 835 ~ && let Err(err) = 836 | apply_camera_input(app_handle.clone(), app_handle.state(), Some(camera)).await 837 | { 838 | warn!(%err, "Failed to restore camera input"); 839 ~ } |
});
}

#[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