Skip to content

Commit 7ded393

Browse files
authored
Merge branch 'main' into main
2 parents ff4491a + 38f23fc commit 7ded393

File tree

12 files changed

+480
-220
lines changed

12 files changed

+480
-220
lines changed

apps/desktop/src-tauri/src/general_settings.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,35 @@ use tauri::{AppHandle, Wry};
55
use tauri_plugin_store::StoreExt;
66
use uuid::Uuid;
77

8-
#[derive(Default, Serialize, Deserialize, Type, Debug)]
8+
#[derive(Default, Serialize, Deserialize, Type, Debug, Clone, Copy)]
99
#[serde(rename_all = "camelCase")]
1010
pub enum PostStudioRecordingBehaviour {
1111
#[default]
1212
OpenEditor,
1313
ShowOverlay,
1414
}
1515

16-
#[derive(Default, Serialize, Deserialize, Type, Debug)]
16+
#[derive(Default, Serialize, Deserialize, Type, Debug, Clone, Copy)]
1717
#[serde(rename_all = "camelCase")]
1818
pub enum MainWindowRecordingStartBehaviour {
1919
#[default]
2020
Close,
2121
Minimise,
2222
}
2323

24-
#[derive(Serialize, Deserialize, Type, Debug)]
24+
impl MainWindowRecordingStartBehaviour {
25+
pub fn perform(&self, window: &tauri::WebviewWindow) -> tauri::Result<()> {
26+
match self {
27+
Self::Close => window.close(),
28+
Self::Minimise => window.minimize(),
29+
}
30+
}
31+
}
32+
33+
// When adding fields here, #[serde(default)] defines the value to use for existing configurations,
34+
// and `Default::default` defines the value to use for new configurations.
35+
// Things that affect the user experience should only be enabled by default for new configurations.
36+
#[derive(Serialize, Deserialize, Type, Debug, Clone)]
2537
#[serde(rename_all = "camelCase")]
2638
pub struct GeneralSettingsStore {
2739
#[serde(default = "uuid::Uuid::new_v4")]
@@ -57,6 +69,8 @@ pub struct GeneralSettingsStore {
5769
pub custom_cursor_capture: bool,
5870
#[serde(default = "default_server_url")]
5971
pub server_url: String,
72+
#[serde(default)]
73+
pub recording_countdown: Option<u32>,
6074
#[serde(default, alias = "open_editor_after_recording")]
6175
#[deprecated]
6276
_open_editor_after_recording: bool,
@@ -68,7 +82,7 @@ fn default_server_url() -> String {
6882
.to_string()
6983
}
7084

71-
#[derive(Serialize, Deserialize, Type, Debug)]
85+
#[derive(Serialize, Deserialize, Type, Debug, Clone)]
7286
#[serde(rename_all = "camelCase")]
7387
pub struct CommercialLicense {
7488
license_key: String,
@@ -96,6 +110,7 @@ impl Default for GeneralSettingsStore {
96110
main_window_recording_start_behaviour: MainWindowRecordingStartBehaviour::Close,
97111
custom_cursor_capture: false,
98112
server_url: default_server_url(),
113+
recording_countdown: Some(3),
99114
_open_editor_after_recording: false,
100115
}
101116
}

apps/desktop/src-tauri/src/lib.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,22 @@ impl App {
151151
}
152152
}
153153
}
154+
155+
async fn add_recording_logging_handle(&mut self, path: &PathBuf) -> Result<(), String> {
156+
let logfile =
157+
std::fs::File::create(path).map_err(|e| format!("Failed to create logfile: {e}"))?;
158+
159+
self.recording_logging_handle
160+
.reload(Some(Box::new(
161+
tracing_subscriber::fmt::layer()
162+
.with_ansi(false)
163+
.with_target(true)
164+
.with_writer(logfile),
165+
) as DynLoggingLayer))
166+
.map_err(|e| format!("Failed to reload logging layer: {e}"))?;
167+
168+
Ok(())
169+
}
154170
}
155171

156172
#[tauri::command]
@@ -820,6 +836,13 @@ async fn get_editor_meta(editor: WindowEditorInstance) -> Result<RecordingMeta,
820836
let path = editor.project_path.clone();
821837
RecordingMeta::load_for_project(&path).map_err(|e| e.to_string())
822838
}
839+
#[tauri::command]
840+
#[specta::specta]
841+
async fn set_pretty_name(editor: WindowEditorInstance, pretty_name: String) -> Result<(), String> {
842+
let mut meta = editor.meta().clone();
843+
meta.pretty_name = pretty_name;
844+
meta.save_for_project().map_err(|e| e.to_string())
845+
}
823846

824847
#[tauri::command]
825848
#[specta::specta]
@@ -1852,6 +1875,7 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
18521875
update_auth_plan,
18531876
set_window_transparent,
18541877
get_editor_meta,
1878+
set_pretty_name,
18551879
set_server_url,
18561880
set_camera_preview_state,
18571881
await_camera_preview_ready,
@@ -1882,6 +1906,7 @@ pub async fn run(recording_logging_handle: LoggingHandle) {
18821906
audio_meter::AudioInputLevelChange,
18831907
UploadProgress,
18841908
captions::DownloadProgress,
1909+
recording::RecordingEvent
18851910
])
18861911
.error_handling(tauri_specta::ErrorHandlingMode::Throw)
18871912
.typ::<ProjectConfiguration>()

apps/desktop/src-tauri/src/recording.rs

Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ use crate::{
66
audio::AppSounds,
77
auth::AuthStore,
88
create_screenshot,
9-
general_settings::{
10-
GeneralSettingsStore, MainWindowRecordingStartBehaviour, PostStudioRecordingBehaviour,
11-
},
9+
general_settings::{GeneralSettingsStore, PostStudioRecordingBehaviour},
1210
open_external_link,
1311
presets::PresetsStore,
1412
upload::{
@@ -202,6 +200,15 @@ pub struct StartRecordingInputs {
202200
pub mode: RecordingMode,
203201
}
204202

203+
#[derive(tauri_specta::Event, specta::Type, Clone, Debug, serde::Serialize)]
204+
#[serde(tag = "variant")]
205+
pub enum RecordingEvent {
206+
Countdown { value: u32 },
207+
Started,
208+
Stopped,
209+
Failed { error: String },
210+
}
211+
205212
#[tauri::command]
206213
#[specta::specta]
207214
#[tracing::instrument(name = "recording", skip_all)]
@@ -211,6 +218,8 @@ pub async fn start_recording(
211218
inputs: StartRecordingInputs,
212219
) -> Result<(), String> {
213220
let id = uuid::Uuid::new_v4().to_string();
221+
let general_settings = GeneralSettingsStore::get(&app).ok().flatten();
222+
let general_settings = general_settings.as_ref();
214223

215224
let recording_dir = app
216225
.path()
@@ -220,20 +229,11 @@ pub async fn start_recording(
220229
.join(format!("{id}.cap"));
221230

222231
ensure_dir(&recording_dir).map_err(|e| format!("Failed to create recording directory: {e}"))?;
223-
let logfile = std::fs::File::create(recording_dir.join("recording-logs.log"))
224-
.map_err(|e| format!("Failed to create logfile: {e}"))?;
225-
226232
state_mtx
227233
.write()
228234
.await
229-
.recording_logging_handle
230-
.reload(Some(Box::new(
231-
tracing_subscriber::fmt::layer()
232-
.with_ansi(false)
233-
.with_target(true)
234-
.with_writer(logfile),
235-
) as DynLoggingLayer))
236-
.map_err(|e| format!("Failed to reload logging layer: {e}"))?;
235+
.add_recording_logging_handle(&recording_dir.join("recording-logs.log"))
236+
.await?;
237237

238238
let target_name = {
239239
let title = inputs.capture_target.get_title();
@@ -325,6 +325,28 @@ pub async fn start_recording(
325325
_ => {}
326326
}
327327

328+
if let Some(window) = CapWindowId::Main.get(&app) {
329+
let _ = general_settings
330+
.map(|v| v.main_window_recording_start_behaviour)
331+
.unwrap_or_default()
332+
.perform(&window);
333+
}
334+
335+
let countdown = general_settings.and_then(|v| v.recording_countdown);
336+
let _ = ShowCapWindow::InProgressRecording { countdown }
337+
.show(&app)
338+
.await;
339+
340+
if let Some(countdown) = countdown {
341+
for t in 0..countdown {
342+
let _ = RecordingEvent::Countdown {
343+
value: countdown - t,
344+
}
345+
.emit(&app);
346+
tokio::time::sleep(Duration::from_secs(1)).await;
347+
}
348+
}
349+
328350
let (finish_upload_tx, finish_upload_rx) = flume::bounded(1);
329351
let progressive_upload = video_upload_info
330352
.as_ref()
@@ -344,7 +366,7 @@ pub async fn start_recording(
344366
// done in spawn to catch panics just in case
345367
let actor_done_rx = spawn_actor({
346368
let state_mtx = Arc::clone(&state_mtx);
347-
let app = app.clone();
369+
let general_settings = general_settings.cloned();
348370
async move {
349371
fail!("recording::spawn_actor");
350372
let mut state = state_mtx.write().await;
@@ -362,9 +384,7 @@ pub async fn start_recording(
362384
recording_dir.clone(),
363385
base_inputs,
364386
state.camera_feed.clone(),
365-
GeneralSettingsStore::get(&app)
366-
.ok()
367-
.flatten()
387+
general_settings
368388
.map(|s| s.custom_cursor_capture)
369389
.unwrap_or_default(),
370390
)
@@ -423,6 +443,8 @@ pub async fn start_recording(
423443
.await
424444
.map_err(|e| format!("Failed to spawn recording actor: {}", e))??;
425445

446+
let _ = RecordingEvent::Started.emit(&app);
447+
426448
spawn_actor({
427449
let app = app.clone();
428450
let state_mtx = Arc::clone(&state_mtx);
@@ -431,11 +453,14 @@ pub async fn start_recording(
431453
match actor_done_rx.await {
432454
Ok(Ok(_)) => {
433455
let _ = finish_upload_tx.send(());
456+
let _ = RecordingEvent::Stopped.emit(&app);
434457
return;
435458
}
436459
Ok(Err(e)) => {
437460
let mut state = state_mtx.write().await;
438461

462+
let _ = RecordingEvent::Failed { error: e.clone() }.emit(&app);
463+
439464
let mut dialog = MessageDialogBuilder::new(
440465
app.dialog().clone(),
441466
format!("An error occurred"),
@@ -457,30 +482,6 @@ pub async fn start_recording(
457482
}
458483
});
459484

460-
if let Some(window) = CapWindowId::Main.get(&app) {
461-
match GeneralSettingsStore::get(&app)
462-
.ok()
463-
.flatten()
464-
.map(|s| s.main_window_recording_start_behaviour)
465-
.unwrap_or_default()
466-
{
467-
MainWindowRecordingStartBehaviour::Close => {
468-
let _ = window.close();
469-
}
470-
MainWindowRecordingStartBehaviour::Minimise => {
471-
let _ = window.minimize();
472-
}
473-
}
474-
}
475-
476-
if let Some(window) = CapWindowId::InProgressRecording.get(&app) {
477-
window.eval("window.location.reload()").ok();
478-
} else {
479-
let _ = ShowCapWindow::InProgressRecording { position: None }
480-
.show(&app)
481-
.await;
482-
}
483-
484485
AppSounds::StartRecording.play();
485486

486487
RecordingStarted.emit(&app).ok();
@@ -594,8 +595,11 @@ async fn handle_recording_end(
594595
// Clear current recording, just in case :)
595596
app.current_recording.take();
596597

597-
if let Some(recording) = recording {
598-
handle_recording_finish(&handle, recording).await?;
598+
let res = if let Some(recording) = recording {
599+
// we delay reporting errors here so that everything else happens first
600+
Some(handle_recording_finish(&handle, recording).await)
601+
} else {
602+
None
599603
};
600604

601605
let _ = RecordingStopped.emit(&handle);
@@ -618,6 +622,10 @@ async fn handle_recording_end(
618622

619623
CurrentRecordingChanged.emit(&handle).ok();
620624

625+
if let Some(res) = res {
626+
res?;
627+
}
628+
621629
Ok(())
622630
}
623631

apps/desktop/src-tauri/src/windows.rs

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,17 @@
11
#![allow(unused_mut)]
22
#![allow(unused_imports)]
33

4-
use crate::{
5-
App, ArcLock, camera::CameraPreview, fake_window, general_settings::AppTheme, permissions,
6-
};
4+
use crate::{App, ArcLock, fake_window, general_settings::AppTheme, permissions};
75
use cap_flags::FLAGS;
86
use cap_media::{platform::logical_monitor_bounds, sources::CaptureScreen};
9-
use futures::{executor::block_on, pin_mut};
7+
use futures::pin_mut;
108
use serde::Deserialize;
119
use specta::Type;
1210
use std::{
13-
borrow::Cow,
1411
ops::Deref,
1512
path::PathBuf,
1613
str::FromStr,
17-
sync::{Arc, Mutex, atomic::AtomicU32, mpsc},
14+
sync::{Arc, Mutex, atomic::AtomicU32},
1815
};
1916
use tauri::{
2017
AppHandle, LogicalPosition, Manager, Monitor, PhysicalPosition, PhysicalSize, WebviewUrl,
@@ -133,14 +130,6 @@ impl CapWindowId {
133130
app.get_webview_window(&label)
134131
}
135132

136-
#[cfg(target_os = "windows")]
137-
pub fn should_have_decorations(&self) -> bool {
138-
matches!(
139-
self,
140-
Self::Setup | Self::Settings | Self::Editor { .. } | Self::ModeSelect
141-
)
142-
}
143-
144133
#[cfg(target_os = "macos")]
145134
pub fn traffic_lights_position(&self) -> Option<Option<LogicalPosition<f64>>> {
146135
match self {
@@ -320,6 +309,8 @@ impl ShowCapWindow {
320309
Self::Camera => {
321310
const WINDOW_SIZE: f64 = 230.0 * 2.0;
322311

312+
let port = app.state::<Arc<RwLock<App>>>().read().await.camera_ws_port;
313+
323314
let mut window_builder = self
324315
.window_builder(app, "/camera")
325316
.maximized(false)
@@ -335,8 +326,13 @@ impl ShowCapWindow {
335326
- WINDOW_SIZE
336327
- 100.0,
337328
)
338-
.transparent(true)
339-
.visible(false); // We set this true in `CameraWindowState::init_window`
329+
.initialization_script(&format!(
330+
"
331+
window.__CAP__ = window.__CAP__ ?? {{}};
332+
window.__CAP__.cameraWsPort = {port};
333+
",
334+
))
335+
.transparent(true);
340336

341337
let window = window_builder.build()?;
342338

@@ -439,7 +435,8 @@ impl ShowCapWindow {
439435
Self::InProgressRecording {
440436
position: _position,
441437
} => {
442-
let width = 244.0;
438+
let mut width = 180.0 + 32.0;
439+
443440
let height = 40.0;
444441

445442
let window = self

0 commit comments

Comments
 (0)