diff --git a/apps/desktop/src-tauri/src/fake_window.rs b/apps/desktop/src-tauri/src/fake_window.rs index 31d35d6f56..1826d3e127 100644 --- a/apps/desktop/src-tauri/src/fake_window.rs +++ b/apps/desktop/src-tauri/src/fake_window.rs @@ -71,10 +71,18 @@ fn calculate_bottom_center_position(display: &Display) -> Option<(f64, f64)> { } pub fn spawn_fake_window_listener(app: AppHandle, window: WebviewWindow) { - window.set_ignore_cursor_events(true).ok(); - let is_recording_controls = window.label() == RECORDING_CONTROLS_LABEL; + #[cfg(target_os = "linux")] + if is_recording_controls { + window.set_ignore_cursor_events(false).ok(); + } else { + window.set_ignore_cursor_events(true).ok(); + } + + #[cfg(not(target_os = "linux"))] + window.set_ignore_cursor_events(true).ok(); + tokio::spawn(async move { let state = app.state::(); let mut current_display_id: Option = get_display_id_for_cursor(); @@ -94,9 +102,23 @@ pub fn spawn_fake_window_listener(app: AppHandle, window: WebviewWindow) { } } + #[cfg(target_os = "linux")] + if is_recording_controls { + window.set_ignore_cursor_events(false).ok(); + continue; + } + let map = state.0.read().await; let Some(windows) = map.get(window.label()) else { + #[cfg(target_os = "linux")] + if is_recording_controls { + window.set_ignore_cursor_events(false).ok(); + } else { + window.set_ignore_cursor_events(true).ok(); + } + + #[cfg(not(target_os = "linux"))] window.set_ignore_cursor_events(true).ok(); continue; }; @@ -106,6 +128,14 @@ pub fn spawn_fake_window_listener(app: AppHandle, window: WebviewWindow) { window.cursor_position(), window.scale_factor(), ) else { + #[cfg(target_os = "linux")] + if is_recording_controls { + let _ = window.set_ignore_cursor_events(false); + } else { + let _ = window.set_ignore_cursor_events(true); + } + + #[cfg(not(target_os = "linux"))] let _ = window.set_ignore_cursor_events(true); continue; }; @@ -113,18 +143,26 @@ pub fn spawn_fake_window_listener(app: AppHandle, window: WebviewWindow) { let mut ignore = true; for bounds in windows.values() { - let x_min = (window_position.x as f64) + bounds.position().x() * scale_factor; - let x_max = (window_position.x as f64) - + (bounds.position().x() + bounds.size().width()) * scale_factor; - let y_min = (window_position.y as f64) + bounds.position().y() * scale_factor; - let y_max = (window_position.y as f64) - + (bounds.position().y() + bounds.size().height()) * scale_factor; - - if mouse_position.x >= x_min - && mouse_position.x <= x_max - && mouse_position.y >= y_min - && mouse_position.y <= y_max - { + let local_x_min = bounds.position().x() * scale_factor; + let local_x_max = (bounds.position().x() + bounds.size().width()) * scale_factor; + let local_y_min = bounds.position().y() * scale_factor; + let local_y_max = (bounds.position().y() + bounds.size().height()) * scale_factor; + + let global_x_min = (window_position.x as f64) + local_x_min; + let global_x_max = (window_position.x as f64) + local_x_max; + let global_y_min = (window_position.y as f64) + local_y_min; + let global_y_max = (window_position.y as f64) + local_y_max; + + let in_local_bounds = mouse_position.x >= local_x_min + && mouse_position.x <= local_x_max + && mouse_position.y >= local_y_min + && mouse_position.y <= local_y_max; + let in_global_bounds = mouse_position.x >= global_x_min + && mouse_position.x <= global_x_max + && mouse_position.y >= global_y_min + && mouse_position.y <= global_y_max; + + if in_local_bounds || in_global_bounds { ignore = false; break; } diff --git a/apps/desktop/src-tauri/src/recording.rs b/apps/desktop/src-tauri/src/recording.rs index 4e3852ab61..0732e98f0a 100644 --- a/apps/desktop/src-tauri/src/recording.rs +++ b/apps/desktop/src-tauri/src/recording.rs @@ -1236,7 +1236,8 @@ fn mic_actor_not_running(err: &anyhow::Error) -> bool { #[instrument(skip(app, state))] pub async fn stop_recording(app: AppHandle, state: MutableState<'_, App>) -> Result<(), String> { let mut state = state.write().await; - let Some(current_recording) = state.clear_current_recording() else { + let current_recording = state.clear_current_recording(); + let Some(current_recording) = current_recording else { return Err("Recording not in progress".to_string())?; }; diff --git a/apps/desktop/src-tauri/src/tray.rs b/apps/desktop/src-tauri/src/tray.rs index 3cd7cafbe6..c16709f1ec 100644 --- a/apps/desktop/src-tauri/src/tray.rs +++ b/apps/desktop/src-tauri/src/tray.rs @@ -1,5 +1,5 @@ use crate::{ - NewScreenshotAdded, NewStudioRecordingAdded, RecordingStarted, RecordingStopped, + App, NewScreenshotAdded, NewStudioRecordingAdded, RecordingStarted, RecordingStopped, RequestOpenSettings, recording, recording_settings::{RecordingSettingsStore, RecordingTargetMode}, windows::ShowCapWindow, @@ -817,12 +817,21 @@ pub fn create_tray(app: &AppHandle) -> tauri::Result<()> { let app_handle = app.clone(); move |tray, event| { if let tauri::tray::TrayIconEvent::Click { .. } = event { - if is_recording.load(Ordering::Relaxed) { + let is_recording_now = is_recording.load(Ordering::Relaxed); + if is_recording_now { let app = app_handle.clone(); tokio::spawn(async move { let _ = recording::stop_recording(app.clone(), app.state()).await; }); } else { + let app = app_handle.clone(); + tokio::spawn(async move { + let state = + app.state::>>().inner().clone(); + if state.read().await.is_recording_active_or_pending() { + let _ = recording::stop_recording(app.clone(), app.state()).await; + } + }); let _ = tray.set_visible(true); } } diff --git a/apps/desktop/src-tauri/src/windows.rs b/apps/desktop/src-tauri/src/windows.rs index 48c26564f8..889158944d 100644 --- a/apps/desktop/src-tauri/src/windows.rs +++ b/apps/desktop/src-tauri/src/windows.rs @@ -877,6 +877,13 @@ impl ShowCapWindow { let recording_monitor = CursorMonitorInfo::get(); let (pos_x, pos_y) = recording_monitor.bottom_center_position(width, height, 120.0); let _ = window.set_position(tauri::LogicalPosition::new(pos_x, pos_y)); + #[cfg(target_os = "linux")] + if let Err(error) = window.set_ignore_cursor_events(false) { + warn!( + %error, + "Failed to make reused recording controls interactive on linux" + ); + } window.show().ok(); window.set_focus().ok(); return Ok(window); @@ -1944,6 +1951,12 @@ impl ShowCapWindow { #[cfg(target_os = "linux")] { tokio::time::sleep(std::time::Duration::from_millis(100)).await; + if let Err(error) = window.set_ignore_cursor_events(false) { + warn!( + %error, + "Failed to make new recording controls interactive on linux" + ); + } window.show().ok(); window.set_focus().ok(); fake_window::spawn_fake_window_listener(app.clone(), window.clone()); diff --git a/apps/desktop/src/app.tsx b/apps/desktop/src/app.tsx new file mode 100644 index 0000000000..8ce017e646 --- /dev/null +++ b/apps/desktop/src/app.tsx @@ -0,0 +1 @@ +export { default } from "./App"; diff --git a/apps/desktop/src/utils/tauri.ts b/apps/desktop/src/utils/tauri.ts index a0bf31789c..b40e0988e0 100644 --- a/apps/desktop/src/utils/tauri.ts +++ b/apps/desktop/src/utils/tauri.ts @@ -489,7 +489,6 @@ export type JsonValue = [T] export type LogicalBounds = { position: LogicalPosition; size: LogicalSize } export type LogicalPosition = { x: number; y: number } export type LogicalSize = { width: number; height: number } -export type MacOSVersionInfo = { major: number; minor: number; patch: number; displayName: string; buildNumber: string; isAppleSilicon: boolean } export type MainWindowRecordingStartBehaviour = "close" | "minimise" export type MaskKeyframes = { position?: MaskVectorKeyframe[]; size?: MaskVectorKeyframe[]; intensity?: MaskScalarKeyframe[] } export type MaskKind = "sensitive" | "highlight" @@ -512,7 +511,7 @@ export type OnEscapePress = null export type Organization = { id: string; name: string; ownerId: string } export type PhysicalSize = { width: number; height: number } export type Plan = { upgraded: boolean; manual: boolean; last_checked: number } -export type Platform = "MacOS" | "Windows" +export type Platform = "MacOS" | "Windows" | "Linux" export type PostDeletionBehaviour = "doNothing" | "reopenRecordingWindow" export type PostStudioRecordingBehaviour = "openEditor" | "showOverlay" export type Preset = { name: string; config: ProjectConfiguration } @@ -555,7 +554,7 @@ export type StartRecordingInputs = { capture_target: ScreenCaptureTarget; captur export type StereoMode = "stereo" | "monoL" | "monoR" export type StudioRecordingMeta = { segment: SingleSegment } | { inner: MultipleSegments } export type StudioRecordingStatus = { status: "InProgress" } | { status: "NeedsRemux" } | { status: "Failed"; error: string } | { status: "Complete" } -export type SystemDiagnostics = { macosVersion: MacOSVersionInfo | null; availableEncoders: string[]; screenCaptureSupported: boolean; metalSupported: boolean; gpuName: string | null } +export type SystemDiagnostics = { kernelVersion: string | null; availableEncoders: string[]; displayServer: string | null } export type TargetUnderCursor = { display_id: DisplayId | null; window: WindowUnderCursor | null } export type TextSegment = { start: number; end: number; enabled?: boolean; content?: string; center?: XY; size?: XY; fontFamily?: string; fontSize?: number; fontWeight?: number; italic?: boolean; color?: string; fadeDuration?: number } export type TimelineConfiguration = { segments: TimelineSegment[]; zoomSegments: ZoomSegment[]; sceneSegments?: SceneSegment[]; maskSegments?: MaskSegment[]; textSegments?: TextSegment[] }