Skip to content

Commit 48645d5

Browse files
Merge pull request #1720 from CapSoftware/0.4.84-fixes
0.4.84 fixes
2 parents 6fd5daa + a76076a commit 48645d5

18 files changed

Lines changed: 836 additions & 283 deletions

File tree

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

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ impl Renderer {
624624
let mut is_paused = false;
625625

626626
let mut state = default_state;
627+
let mut needs_full_reconfigure = false;
627628

628629
let pause_and_hide = || {
629630
window
@@ -642,6 +643,7 @@ impl Renderer {
642643
match reconfigure.recv().await {
643644
Ok(ReconfigureEvent::Resume) => {
644645
is_paused = false;
646+
while camera_rx.try_recv().is_ok() {}
645647
break;
646648
}
647649
Ok(ReconfigureEvent::Shutdown) => {
@@ -650,6 +652,7 @@ impl Renderer {
650652
}
651653
Ok(ReconfigureEvent::State(new_state)) => {
652654
state = new_state;
655+
needs_full_reconfigure = true;
653656
}
654657
Ok(ReconfigureEvent::WindowResized { width, height }) => {
655658
self.reconfigure_gpu_surface(width, height);
@@ -662,14 +665,35 @@ impl Renderer {
662665
}
663666
}
664667

665-
let event = loop {
666-
let timeout_remaining = if received_first_frame {
667-
Duration::MAX
668-
} else {
669-
startup_timeout.saturating_sub(start_time.elapsed())
670-
};
668+
if needs_full_reconfigure {
669+
needs_full_reconfigure = false;
670+
self.update_state_uniforms(&state);
671+
let aspect_ratio = self.aspect_ratio.get_latest_key().copied().unwrap_or(
672+
if state.shape == CameraPreviewShape::Full {
673+
16.0 / 9.0
674+
} else {
675+
1.0
676+
},
677+
);
678+
self.aspect_ratio = Cached::default();
679+
if let Ok((width, height)) = resize_window(&window, &state, aspect_ratio, false)
680+
.await
681+
.map_err(|err| {
682+
error!("Error resizing camera preview window after resume: {err}")
683+
})
684+
{
685+
self.reconfigure_gpu_surface(width, height);
686+
}
687+
}
688+
689+
let frame_timeout = if received_first_frame {
690+
Duration::from_secs(5)
691+
} else {
692+
startup_timeout.saturating_sub(start_time.elapsed())
693+
};
671694

672-
if timeout_remaining.is_zero() {
695+
let event = loop {
696+
if frame_timeout.is_zero() {
673697
warn!(
674698
"Camera preview timed out waiting for first frame, entering paused state"
675699
);
@@ -696,8 +720,8 @@ impl Renderer {
696720
continue;
697721
}
698722
},
699-
_ = tokio::time::sleep(timeout_remaining) => {
700-
warn!("Camera preview timed out waiting for first frame, entering paused state");
723+
_ = tokio::time::sleep(frame_timeout) => {
724+
warn!("Camera preview: no frames received within timeout, entering paused state");
701725
is_paused = true;
702726
pause_and_hide();
703727
continue 'main_loop;
@@ -808,8 +832,7 @@ impl Renderer {
808832
.ok();
809833
}
810834
Err(ReconfigureEvent::Resume) => {
811-
// On resume, we just need to present a frame to wake up the surface.
812-
// The actual window.show() happens in windows.rs ShowCapWindow::Camera.
835+
while camera_rx.try_recv().is_ok() {}
813836
if let Some(surface) = &self.surface
814837
&& let Ok(texture) = surface.get_current_texture()
815838
{

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

Lines changed: 83 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1-
use scap_targets::{Display, DisplayId, bounds::LogicalBounds};
1+
use cap_recording::sources::screen_capture::ScreenCaptureTarget;
2+
use scap_targets::{Display, DisplayId, Window as ScapWindow, bounds::LogicalBounds};
23
use std::{collections::HashMap, sync::Arc, time::Duration};
34
use tauri::{AppHandle, Manager, WebviewWindow};
45
use tokio::{sync::RwLock, time::sleep};
56
use tracing::instrument;
67

8+
use crate::{App, ArcLock, RecordingState};
9+
710
const RECORDING_CONTROLS_LABEL: &str = "in-progress-recording";
811
const RECORDING_CONTROLS_WIDTH: f64 = 320.0;
912
const RECORDING_CONTROLS_HEIGHT: f64 = 150.0;
@@ -70,6 +73,37 @@ fn calculate_bottom_center_position(display: &Display) -> Option<(f64, f64)> {
7073
Some((pos_x, pos_y))
7174
}
7275

76+
const TARGET_CONTROLS_OFFSET_Y: f64 = 48.0;
77+
78+
pub fn calculate_recording_controls_position_for_target(
79+
capture_target: &ScreenCaptureTarget,
80+
) -> Option<(f64, f64)> {
81+
match capture_target {
82+
ScreenCaptureTarget::Window { id } => {
83+
let window = ScapWindow::from_id(id)?;
84+
let bounds = window.raw_handle().logical_bounds()?;
85+
let pos_x =
86+
bounds.position().x() + (bounds.size().width() - RECORDING_CONTROLS_WIDTH) / 2.0;
87+
let pos_y = bounds.position().y() + bounds.size().height()
88+
- RECORDING_CONTROLS_HEIGHT
89+
- TARGET_CONTROLS_OFFSET_Y;
90+
Some((pos_x, pos_y))
91+
}
92+
ScreenCaptureTarget::Area { screen, bounds } => {
93+
let display = Display::from_id(screen)?;
94+
let display_bounds = display.raw_handle().logical_bounds()?;
95+
let abs_x = display_bounds.position().x() + bounds.position().x();
96+
let abs_y = display_bounds.position().y() + bounds.position().y();
97+
let pos_x = abs_x + (bounds.size().width() - RECORDING_CONTROLS_WIDTH) / 2.0;
98+
let pos_y = abs_y + bounds.size().height()
99+
- RECORDING_CONTROLS_HEIGHT
100+
- TARGET_CONTROLS_OFFSET_Y;
101+
Some((pos_x, pos_y))
102+
}
103+
_ => None,
104+
}
105+
}
106+
73107
pub fn spawn_fake_window_listener(app: AppHandle, window: WebviewWindow) {
74108
window.set_ignore_cursor_events(true).ok();
75109

@@ -78,19 +112,59 @@ pub fn spawn_fake_window_listener(app: AppHandle, window: WebviewWindow) {
78112
tokio::spawn(async move {
79113
let state = app.state::<FakeWindowBounds>();
80114
let mut current_display_id: Option<DisplayId> = get_display_id_for_cursor();
115+
let mut last_target_pos: Option<(f64, f64)> = None;
81116

82117
loop {
83118
sleep(Duration::from_millis(1000 / 20)).await;
84119

85-
if is_recording_controls && let Some(cursor_display_id) = get_display_id_for_cursor() {
86-
let display_changed = current_display_id.as_ref() != Some(&cursor_display_id);
120+
if is_recording_controls {
121+
let capture_target = app.state::<ArcLock<App>>().try_read().ok().and_then(|s| {
122+
match &s.recording_state {
123+
RecordingState::Pending { target, .. } => Some(target.clone()),
124+
RecordingState::Active(inner) => Some(inner.capture_target().clone()),
125+
RecordingState::None => None,
126+
}
127+
});
128+
129+
let mut handled = false;
130+
131+
if let Some(ref target) = capture_target {
132+
match target {
133+
ScreenCaptureTarget::Window { .. } => {
134+
if let Some((px, py)) =
135+
calculate_recording_controls_position_for_target(target)
136+
{
137+
let changed = match last_target_pos {
138+
Some((lx, ly)) => {
139+
(px - lx).abs() > 0.5 || (py - ly).abs() > 0.5
140+
}
141+
None => true,
142+
};
143+
if changed {
144+
let _ =
145+
window.set_position(tauri::LogicalPosition::new(px, py));
146+
last_target_pos = Some((px, py));
147+
}
148+
handled = true;
149+
}
150+
}
151+
ScreenCaptureTarget::Area { .. } => {
152+
handled = true;
153+
}
154+
_ => {}
155+
}
156+
}
157+
158+
if !handled && let Some(cursor_display_id) = get_display_id_for_cursor() {
159+
let display_changed = current_display_id.as_ref() != Some(&cursor_display_id);
87160

88-
if display_changed
89-
&& let Some(display) = get_display_by_id(&cursor_display_id)
90-
&& let Some((pos_x, pos_y)) = calculate_bottom_center_position(&display)
91-
{
92-
let _ = window.set_position(tauri::LogicalPosition::new(pos_x, pos_y));
93-
current_display_id = Some(cursor_display_id);
161+
if display_changed
162+
&& let Some(display) = get_display_by_id(&cursor_display_id)
163+
&& let Some((pos_x, pos_y)) = calculate_bottom_center_position(&display)
164+
{
165+
let _ = window.set_position(tauri::LogicalPosition::new(pos_x, pos_y));
166+
current_display_id = Some(cursor_display_id);
167+
}
94168
}
95169
}
96170

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -206,14 +206,21 @@ where
206206
}
207207
}
208208

209+
fn force_exit(code: i32) -> ! {
210+
unsafe extern "C" {
211+
fn _exit(code: i32) -> !;
212+
}
213+
unsafe { _exit(code) }
214+
}
215+
209216
fn spawn_exit_watchdog() {
210217
std::thread::spawn(move || {
211218
std::thread::sleep(APP_EXIT_FORCE_TIMEOUT);
212219
error!(
213220
timeout_ms = APP_EXIT_FORCE_TIMEOUT.as_millis(),
214221
"Forcing process exit after shutdown deadline"
215222
);
216-
std::process::exit(0);
223+
force_exit(0);
217224
});
218225
}
219226

@@ -1074,13 +1081,18 @@ async fn cleanup_camera_window(app: AppHandle, session_id: u64) {
10741081
label.starts_with("target-select-overlay-") && window.is_visible().unwrap_or(false)
10751082
});
10761083

1084+
let main_window_visible = CapWindowId::Main
1085+
.get(&app)
1086+
.and_then(|w| w.is_visible().ok())
1087+
.unwrap_or(false);
1088+
10771089
let is_camera_only_mode = recording_settings::RecordingSettingsStore::get(&app)
10781090
.ok()
10791091
.flatten()
10801092
.and_then(|s| s.target)
10811093
.is_some_and(|t| matches!(t, ScreenCaptureTarget::CameraOnly));
10821094

1083-
if is_camera_only_mode {
1095+
if is_camera_only_mode && main_window_visible {
10841096
tracing::info!("Camera cleanup: preserving camera feed for camera-only mode");
10851097
return;
10861098
}
@@ -1202,7 +1214,7 @@ fn finalize_app_exit(app: &AppHandle, exit_code: i32) -> ! {
12021214
}
12031215
});
12041216
match app_exit_action(exit_code) {
1205-
AppExitAction::Process(code) => std::process::exit(code),
1217+
AppExitAction::Process(code) => force_exit(code),
12061218
}
12071219
}
12081220

@@ -4206,7 +4218,7 @@ fn restore_camera_window(app: &AppHandle) {
42064218
let should_restore_camera = app
42074219
.state::<ArcLock<App>>()
42084220
.try_read()
4209-
.map(|state| state.selected_camera_id.is_some())
4221+
.map(|state| state.selected_camera_id.is_some() && !state.camera_cleanup_done)
42104222
.unwrap_or(false);
42114223

42124224
if should_restore_camera {

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

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -655,15 +655,21 @@ pub async fn start_recording(
655655
RecordingMode::Instant => {
656656
match AuthStore::get(&app).ok().flatten() {
657657
Some(_) => {
658-
// Pre-create the video and get the shareable link
658+
let upload_mode =
659+
if matches!(inputs.capture_target, ScreenCaptureTarget::CameraOnly) {
660+
"desktopMP4"
661+
} else {
662+
"desktopSegments"
663+
};
664+
659665
let s3_config = match crate::upload::create_or_get_video_with_mode(
660666
&app,
661667
false,
662668
None,
663669
Some(project_name.clone()),
664670
None,
665671
inputs.organization_id.clone(),
666-
"desktopSegments",
672+
upload_mode,
667673
)
668674
.await
669675
{
@@ -764,9 +770,12 @@ pub async fn start_recording(
764770
win.hide().ok();
765771
}
766772
}
767-
let _ = ShowCapWindow::InProgressRecording { countdown }
768-
.show(&app)
769-
.await;
773+
let _ = ShowCapWindow::InProgressRecording {
774+
countdown,
775+
capture_target: Some(inputs.capture_target.clone()),
776+
}
777+
.show(&app)
778+
.await;
770779

771780
if let Some(window) = CapWindowId::Main.get(&app) {
772781
let _ = general_settings
@@ -1845,27 +1854,36 @@ async fn handle_recording_finish(
18451854
}
18461855

18471856
let app = app.clone();
1848-
let segments_dir = recording_dir.join("content/display");
1857+
let is_camera_only =
1858+
matches!(recording.display_source, ScreenCaptureTarget::CameraOnly);
18491859

18501860
let display_screenshot = screenshots_dir.join("display.jpg");
1851-
let screenshot_task = tokio::spawn({
1852-
let segments_dir = segments_dir.clone();
1853-
let display_screenshot = display_screenshot.clone();
1854-
async move {
1855-
let screenshot_source: Result<PathBuf, String> =
1856-
create_screenshot_source_from_segments(&segments_dir).await;
1857-
match screenshot_source {
1858-
Ok(temp_path) => {
1859-
let result =
1860-
create_screenshot(temp_path.clone(), display_screenshot, None)
1861-
.await;
1862-
let _ = tokio::fs::remove_file(&temp_path).await;
1863-
result
1861+
let screenshot_task = if is_camera_only {
1862+
let output_mp4 = recording_dir.join("content/output.mp4");
1863+
tokio::spawn({
1864+
let display_screenshot = display_screenshot.clone();
1865+
async move { create_screenshot(output_mp4, display_screenshot, None).await }
1866+
})
1867+
} else {
1868+
let segments_dir = recording_dir.join("content/display");
1869+
tokio::spawn({
1870+
let display_screenshot = display_screenshot.clone();
1871+
async move {
1872+
let screenshot_source: Result<PathBuf, String> =
1873+
create_screenshot_source_from_segments(&segments_dir).await;
1874+
match screenshot_source {
1875+
Ok(temp_path) => {
1876+
let result =
1877+
create_screenshot(temp_path.clone(), display_screenshot, None)
1878+
.await;
1879+
let _ = tokio::fs::remove_file(&temp_path).await;
1880+
result
1881+
}
1882+
Err(e) => Err(format!("Failed to create screenshot source: {e}")),
18641883
}
1865-
Err(e) => Err(format!("Failed to create screenshot source: {e}")),
18661884
}
1867-
}
1868-
});
1885+
})
1886+
};
18691887

18701888
let _ = open_external_link(app.clone(), video_upload_info.link.clone());
18711889

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2684,7 +2684,7 @@ mod tests {
26842684
}
26852685

26862686
use futures::StreamExt;
2687-
while let Some(_) = in_flight.next().await {}
2687+
while in_flight.next().await.is_some() {}
26882688
}
26892689
let old_duration = old_start.elapsed();
26902690

@@ -2712,7 +2712,7 @@ mod tests {
27122712
}
27132713

27142714
use futures::StreamExt;
2715-
while let Some(_) = in_flight.next().await {}
2715+
while in_flight.next().await.is_some() {}
27162716
}
27172717
let new_duration = new_start.elapsed();
27182718

0 commit comments

Comments
 (0)