Show recording overlay in macOS fullscreen apps, and when switching between spaces (#361)

* Gitignore `*.local.*`

Allows for local files like `CLAUDE.local.md` and `settings.local.json`

* Support macOS spaces and fullscreen apps
This commit is contained in:
Danny Smith 2025-11-20 03:28:15 +00:00 committed by GitHub
parent 71d0eeed73
commit 5085485e67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 103 additions and 5 deletions

1
.gitignore vendored
View file

@ -11,6 +11,7 @@ node_modules
dist
dist-ssr
*.local
*.local.*
# Editor directories and files
.vscode/*

19
src-tauri/Cargo.lock generated
View file

@ -2528,6 +2528,7 @@ dependencies = [
"tar",
"tauri",
"tauri-build",
"tauri-nspanel",
"tauri-plugin-autostart",
"tauri-plugin-clipboard-manager",
"tauri-plugin-fs",
@ -4305,6 +4306,12 @@ dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "pastey"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
[[package]]
name = "pathdiff"
version = "0.2.3"
@ -6629,6 +6636,18 @@ dependencies = [
"tauri-utils",
]
[[package]]
name = "tauri-nspanel"
version = "2.1.0"
source = "git+https://github.com/ahkohd/tauri-nspanel?branch=v2.1#da9c9a8d4eb7f0524a2508988df1a7d9585b4904"
dependencies = [
"objc2 0.6.3",
"objc2-app-kit",
"objc2-foundation 0.3.2",
"pastey",
"tauri",
]
[[package]]
name = "tauri-plugin"
version = "2.5.1"

View file

@ -85,6 +85,9 @@ windows = { version = "0.61.3", features = [
"Win32_System_Variant",
] }
[target.'cfg(target_os = "macos")'.dependencies]
tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2.1" }
[profile.release]
lto = true
codegen-units = 1

View file

@ -211,7 +211,7 @@ pub fn run() {
// when the variable is unset
let console_filter = build_console_filter();
tauri::Builder::default()
let mut builder = tauri::Builder::default()
.plugin(
LogBuilder::new()
.level(log::LevelFilter::Trace) // Set to most verbose level globally
@ -234,7 +234,14 @@ pub fn run() {
}),
])
.build(),
)
);
#[cfg(target_os = "macos")]
{
builder = builder.plugin(tauri_nspanel::init());
}
builder
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
show_main_window(app);
}))

View file

@ -1,8 +1,29 @@
use crate::settings;
use crate::settings::OverlayPosition;
use enigo::{Enigo, Mouse};
use tauri::{AppHandle, Emitter, Manager, PhysicalPosition, PhysicalSize};
#[cfg(not(target_os = "macos"))]
use log::debug;
use tauri::{AppHandle, Emitter, Manager, PhysicalPosition, PhysicalSize, WebviewWindowBuilder};
#[cfg(not(target_os = "macos"))]
use tauri::WebviewWindowBuilder;
#[cfg(target_os = "macos")]
use tauri::WebviewUrl;
#[cfg(target_os = "macos")]
use tauri_nspanel::{tauri_panel, CollectionBehavior, PanelBuilder, PanelLevel};
#[cfg(target_os = "macos")]
tauri_panel! {
panel!(RecordingOverlayPanel {
config: {
can_become_key_window: false,
is_floating_panel: true
}
})
}
const OVERLAY_WIDTH: f64 = 172.0;
const OVERLAY_HEIGHT: f64 = 36.0;
@ -84,6 +105,7 @@ fn calculate_overlay_position(app_handle: &AppHandle) -> Option<(f64, f64)> {
}
/// Creates the recording overlay window and keeps it hidden by default
#[cfg(not(target_os = "macos"))]
pub fn create_recording_overlay(app_handle: &AppHandle) {
if let Some((x, y)) = calculate_overlay_position(app_handle) {
match WebviewWindowBuilder::new(
@ -118,6 +140,49 @@ pub fn create_recording_overlay(app_handle: &AppHandle) {
}
}
/// Creates the recording overlay panel and keeps it hidden by default (macOS)
#[cfg(target_os = "macos")]
pub fn create_recording_overlay(app_handle: &AppHandle) {
if let Some((x, y)) = calculate_overlay_position(app_handle) {
// PanelBuilder creates a Tauri window then converts it to NSPanel.
// The window remains registered, so get_webview_window() still works.
match PanelBuilder::<_, RecordingOverlayPanel>::new(
app_handle,
"recording_overlay"
)
.url(WebviewUrl::App("src/overlay/index.html".into()))
.title("Recording")
.position(tauri::Position::Logical(tauri::LogicalPosition { x, y }))
.level(PanelLevel::Status)
.size(tauri::Size::Logical(tauri::LogicalSize {
width: OVERLAY_WIDTH,
height: OVERLAY_HEIGHT
}))
.has_shadow(false)
.transparent(true)
.no_activate(true)
.corner_radius(0.0)
.with_window(|w| {
w.decorations(false)
.transparent(true)
})
.collection_behavior(
CollectionBehavior::new()
.can_join_all_spaces()
.full_screen_auxiliary()
)
.build()
{
Ok(panel) => {
let _ = panel.hide();
}
Err(e) => {
log::error!("Failed to create recording overlay panel: {}", e);
}
}
}
}
/// Shows the recording overlay window with fade-in animation
pub fn show_recording_overlay(app_handle: &AppHandle) {
// Check if overlay should be shown based on position setting
@ -126,9 +191,12 @@ pub fn show_recording_overlay(app_handle: &AppHandle) {
return;
}
update_overlay_position(app_handle);
if let Some(overlay_window) = app_handle.get_webview_window("recording_overlay") {
// Update position before showing to prevent flicker from position changes
if let Some((x, y)) = calculate_overlay_position(app_handle) {
let _ = overlay_window.set_position(tauri::Position::Logical(tauri::LogicalPosition { x, y }));
}
let _ = overlay_window.show();
// Emit event to trigger fade-in animation with recording state
let _ = overlay_window.emit("show-overlay", "recording");