Skip to content

Commit c59f1b4

Browse files
Merge pull request #1512 from CapSoftware/external-display-niceties
Improve external display handling and target selection performance
2 parents 09a2321 + 2f6da91 commit c59f1b4

15 files changed

Lines changed: 824 additions & 226 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ impl InitializedCameraPreview {
198198
1.0
199199
};
200200

201-
let size = resize_window(&window, default_state, aspect, true)
201+
let size = resize_window(&window, default_state, aspect, false)
202202
.context("Error resizing Tauri window")?;
203203

204204
let (tx, rx) = oneshot::channel();

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

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
use scap_targets::bounds::LogicalBounds;
1+
use scap_targets::{Display, DisplayId, bounds::LogicalBounds};
22
use std::{collections::HashMap, sync::Arc, time::Duration};
33
use tauri::{AppHandle, Manager, WebviewWindow};
44
use tokio::{sync::RwLock, time::sleep};
55
use tracing::instrument;
66

7+
const RECORDING_CONTROLS_LABEL: &str = "in-progress-recording";
8+
const RECORDING_CONTROLS_WIDTH: f64 = 320.0;
9+
const RECORDING_CONTROLS_HEIGHT: f64 = 150.0;
10+
const RECORDING_CONTROLS_OFFSET_Y: f64 = 120.0;
11+
712
pub struct FakeWindowBounds(pub Arc<RwLock<HashMap<String, HashMap<String, LogicalBounds>>>>);
813

914
#[tauri::command]
@@ -45,15 +50,50 @@ pub async fn remove_fake_window(
4550
Ok(())
4651
}
4752

53+
fn get_display_id_for_cursor() -> Option<DisplayId> {
54+
Display::get_containing_cursor().map(|d| d.id())
55+
}
56+
57+
fn get_display_by_id(id: &DisplayId) -> Option<Display> {
58+
Display::list().into_iter().find(|d| &d.id() == id)
59+
}
60+
61+
fn calculate_bottom_center_position(display: &Display) -> Option<(f64, f64)> {
62+
let bounds = display.raw_handle().logical_bounds()?;
63+
let x = bounds.position().x();
64+
let y = bounds.position().y();
65+
let width = bounds.size().width();
66+
let height = bounds.size().height();
67+
68+
let pos_x = x + (width - RECORDING_CONTROLS_WIDTH) / 2.0;
69+
let pos_y = y + height - RECORDING_CONTROLS_HEIGHT - RECORDING_CONTROLS_OFFSET_Y;
70+
Some((pos_x, pos_y))
71+
}
72+
4873
pub fn spawn_fake_window_listener(app: AppHandle, window: WebviewWindow) {
4974
window.set_ignore_cursor_events(true).ok();
5075

76+
let is_recording_controls = window.label() == RECORDING_CONTROLS_LABEL;
77+
5178
tokio::spawn(async move {
5279
let state = app.state::<FakeWindowBounds>();
80+
let mut current_display_id: Option<DisplayId> = get_display_id_for_cursor();
5381

5482
loop {
5583
sleep(Duration::from_millis(1000 / 20)).await;
5684

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);
87+
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);
94+
}
95+
}
96+
5797
let map = state.0.read().await;
5898

5999
let Some(windows) = map.get(window.label()) else {
@@ -86,7 +126,6 @@ pub fn spawn_fake_window_listener(app: AppHandle, window: WebviewWindow) {
86126
&& mouse_position.y <= y_max
87127
{
88128
ignore = false;
89-
// ShowCapturesPanel.emit(&app).ok();
90129
break;
91130
}
92131
}

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

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -901,6 +901,12 @@ pub struct RequestOpenRecordingPicker {
901901
pub target_mode: Option<RecordingTargetMode>,
902902
}
903903

904+
#[derive(Deserialize, specta::Type, Serialize, tauri_specta::Event, Debug, Clone)]
905+
pub struct RequestSetTargetMode {
906+
pub target_mode: Option<RecordingTargetMode>,
907+
pub display_id: Option<String>,
908+
}
909+
904910
#[derive(Deserialize, specta::Type, Serialize, tauri_specta::Event, Debug, Clone)]
905911
pub struct RequestOpenSettings {
906912
page: String,
@@ -2533,6 +2539,39 @@ async fn update_auth_plan(app: AppHandle) {
25332539
AuthStore::update_auth_plan(&app).await.ok();
25342540
}
25352541

2542+
pub async fn open_target_picker(
2543+
app: &tauri::AppHandle,
2544+
target_mode: recording_settings::RecordingTargetMode,
2545+
) {
2546+
use tauri::Manager;
2547+
2548+
if let Some(window) = CapWindowId::Main.get(app) {
2549+
window.hide().ok();
2550+
}
2551+
2552+
let prewarmed = app.state::<target_select_overlay::PrewarmedOverlays>();
2553+
let state = app.state::<target_select_overlay::WindowFocusManager>();
2554+
let display_id = scap_targets::Display::get_containing_cursor().map(|d| d.id().to_string());
2555+
2556+
let _ = target_select_overlay::open_target_select_overlays(
2557+
app.clone(),
2558+
state,
2559+
prewarmed,
2560+
None,
2561+
display_id.clone(),
2562+
Some(target_mode),
2563+
)
2564+
.await;
2565+
2566+
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
2567+
2568+
let _ = RequestSetTargetMode {
2569+
target_mode: Some(target_mode),
2570+
display_id,
2571+
}
2572+
.emit(app);
2573+
}
2574+
25362575
type FilteredRegistry = tracing_subscriber::layer::Layered<
25372576
tracing_subscriber::filter::FilterFn<fn(m: &tracing::Metadata) -> bool>,
25382577
tracing_subscriber::Registry,
@@ -2650,6 +2689,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
26502689
captions::check_model_exists,
26512690
captions::delete_whisper_model,
26522691
captions::export_captions_srt,
2692+
target_select_overlay::prewarm_target_select_overlays,
26532693
target_select_overlay::open_target_select_overlays,
26542694
target_select_overlay::close_target_select_overlays,
26552695
target_select_overlay::update_camera_overlay_bounds,
@@ -2673,6 +2713,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
26732713
RecordingStopped,
26742714
RequestStartRecording,
26752715
RequestOpenRecordingPicker,
2716+
RequestSetTargetMode,
26762717
RequestOpenSettings,
26772718
RequestScreenCapturePrewarm,
26782719
NewNotification,
@@ -2820,6 +2861,7 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
28202861
general_settings::init(&app);
28212862
fake_window::init(&app);
28222863
app.manage(target_select_overlay::WindowFocusManager::default());
2864+
app.manage(target_select_overlay::PrewarmedOverlays::default());
28232865
app.manage(EditorWindowIds::default());
28242866
app.manage(ScreenshotEditorWindowIds::default());
28252867
#[cfg(target_os = "macos")]
@@ -2831,6 +2873,19 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
28312873

28322874
gpu_context::prewarm_gpu();
28332875

2876+
tokio::spawn({
2877+
let app = app.clone();
2878+
async move {
2879+
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
2880+
let prewarmed = app.state::<target_select_overlay::PrewarmedOverlays>();
2881+
let _ = target_select_overlay::prewarm_target_select_overlays(
2882+
app.clone(),
2883+
prewarmed,
2884+
)
2885+
.await;
2886+
}
2887+
});
2888+
28342889
tokio::spawn({
28352890
let camera_feed = camera_feed.clone();
28362891
let app = app.clone();
@@ -2988,11 +3043,15 @@ pub async fn run(recording_logging_handle: LoggingHandle, logs_dir: PathBuf) {
29883043
});
29893044

29903045
RequestOpenRecordingPicker::listen_any_spawn(&app, async |event, app| {
2991-
let _ = ShowCapWindow::Main {
2992-
init_target_mode: event.target_mode,
3046+
if let Some(target_mode) = event.target_mode {
3047+
open_target_picker(&app, target_mode).await;
3048+
} else {
3049+
let _ = ShowCapWindow::Main {
3050+
init_target_mode: None,
3051+
}
3052+
.show(&app)
3053+
.await;
29933054
}
2994-
.show(&app)
2995-
.await;
29963055
});
29973056

29983057
RequestOpenSettings::listen_any_spawn(&app, async |payload, app| {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ pub struct ScreenshotEditorInstance {
3232
pub config_tx: watch::Sender<ProjectConfiguration>,
3333
pub path: PathBuf,
3434
pub pretty_name: String,
35+
pub image_width: u32,
36+
pub image_height: u32,
3537
}
3638

3739
impl ScreenshotEditorInstance {
@@ -300,6 +302,8 @@ impl ScreenshotEditorInstances {
300302
config_tx,
301303
path: path.clone(),
302304
pretty_name: recording_meta.pretty_name.clone(),
305+
image_width: width,
306+
image_height: height,
303307
});
304308

305309
// Spawn render loop
@@ -429,6 +433,8 @@ pub struct SerializedScreenshotEditorInstance {
429433
pub path: PathBuf,
430434
pub config: Option<ProjectConfiguration>,
431435
pub pretty_name: String,
436+
pub image_width: u32,
437+
pub image_height: u32,
432438
}
433439

434440
#[tauri::command]
@@ -459,6 +465,8 @@ pub async fn create_screenshot_editor_instance(
459465
path: instance.path.clone(),
460466
config: Some(config),
461467
pretty_name: instance.pretty_name.clone(),
468+
image_width: instance.image_width,
469+
image_height: instance.image_height,
462470
})
463471
}
464472

0 commit comments

Comments
 (0)