Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 2 additions & 69 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions apps/native/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,6 @@ tauri-plugin-updater = "2.0.0"
url = "2"

[target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.26"
objc = "0.2"
window-vibrancy = "0.6"
core-foundation = "0.10"
security-framework = "3"
Expand Down Expand Up @@ -186,6 +184,16 @@ objc2-foundation = { version = "0.3.2", default-features = false, features = [
] }
block2 = "0.6"
dispatch2 = "0.3"
# Typed `NSWindow` bindings for the floating-panel setup in `peek.rs` and the
# `NIXMAC_E2E_OPAQUE_WINDOW` diagnostic in `main.rs`. Already in the tree
# through Tauri; declared directly with the exact features this code uses, so
# the build does not depend on Tauri's own feature selection.
objc2-app-kit = { version = "0.3.2", default-features = false, features = [
"std",
"NSResponder",
"NSWindow",
"objc2-core-foundation",
] }

[target.'cfg(unix)'.dependencies]
nix = { version = "0.31", features = ["user"] }
Expand Down
27 changes: 12 additions & 15 deletions apps/native/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -938,26 +938,23 @@ use window_vibrancy::{apply_vibrancy, NSVisualEffectMaterial, NSVisualEffectStat

#[cfg(target_os = "macos")]
if e2e_opaque_window {
use objc::msg_send;
use objc::runtime::Object;
use objc::sel;
use objc::sel_impl;
use objc2_app_kit::NSWindow;

match main_window.ns_window() {
Ok(ns_window) => unsafe {
let ns_window = ns_window as *mut Object;
let is_opaque: bool = msg_send![ns_window, isOpaque];
let alpha_value: f64 = msg_send![ns_window, alphaValue];
let level: i64 = msg_send![ns_window, level];
let has_shadow: bool = msg_send![ns_window, hasShadow];
Ok(ns_window) => {
// SAFETY: `ns_window()` hands back a
// retained-then-autoreleased NSWindow, valid for this
// scope, and `setup()` runs on the main thread, as
// main-thread-only `NSWindow` requires.
let ns_window: &NSWindow = unsafe { &*ns_window.cast() };
log::info!(
"NIXMAC_E2E_OPAQUE_WINDOW native window diagnostics: isOpaque={} alphaValue={:.3} level={} hasShadow={}",
is_opaque,
alpha_value,
level,
has_shadow
ns_window.isOpaque(),
ns_window.alphaValue(),
ns_window.level(),
ns_window.hasShadow()
);
},
}
Err(error) => {
log::warn!(
"NIXMAC_E2E_OPAQUE_WINDOW native window diagnostics unavailable: {}",
Expand Down
96 changes: 36 additions & 60 deletions apps/native/src-tauri/src/peek.rs
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,38 @@ pub fn get_preview_indicator_state() -> PreviewIndicatorState {
}
}

/// Makes `window` an independent floating panel: above regular windows, present
/// on every space without following the active one, and skipped by Cmd+`.
///
/// Main-thread only, because `NSWindow` is `MainThreadOnly` — call it from
/// `setup()`, not from a command worker thread.
#[cfg(target_os = "macos")]
fn set_floating_panel_behavior<R: Runtime>(window: &tauri::WebviewWindow<R>) {
use objc2_app_kit::{NSFloatingWindowLevel, NSWindow, NSWindowCollectionBehavior};

debug_assert!(
objc2::MainThreadMarker::new().is_some(),
"set_floating_panel_behavior must run on the main thread"
);

let _ = window.set_shadow(false);

let Ok(ns_window) = window.ns_window() else {
return;
};
// SAFETY: `ns_window()` hands back a retained-then-autoreleased NSWindow,
// valid for this scope. `NSWindow` is main-thread-only, and both callers
// run in `setup()` on the main thread.
let ns_window: &NSWindow = unsafe { &*ns_window.cast() };

ns_window.setCollectionBehavior(
NSWindowCollectionBehavior::CanJoinAllSpaces
| NSWindowCollectionBehavior::Stationary
| NSWindowCollectionBehavior::IgnoresCycle,
);
ns_window.setLevel(NSFloatingWindowLevel);
}

/// Creates the preview indicator window (call once during setup).
///
/// The window stays hidden until the preview-indicator React tree has a
Expand Down Expand Up @@ -530,38 +562,8 @@ pub fn create_preview_indicator_window<R: Runtime>(app: &AppHandle<R>) -> Result
#[cfg(not(target_os = "macos"))]
let _ = &window;

// Disable shadow and set as utility panel (prevents grouping with main window)
#[cfg(target_os = "macos")]
{
use cocoa::appkit::{NSWindow, NSWindowCollectionBehavior};
use cocoa::base::id;
use objc::msg_send;
use objc::sel;
use objc::sel_impl;

let _ = window.set_shadow(false);

// Get the native NSWindow and configure it as an independent panel
if let Ok(ns_window) = window.ns_window() {
let ns_win = ns_window as id;
unsafe {
// Set collection behavior to be independent (not grouped with other windows)
// NSWindowCollectionBehaviorStationary keeps it from moving with spaces
// NSWindowCollectionBehaviorCanJoinAllSpaces makes it visible on all spaces
// NSWindowCollectionBehaviorIgnoresCycle prevents Cmd+` from cycling to it
ns_win.setCollectionBehavior_(
NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorStationary
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorIgnoresCycle,
);

// Set as a floating panel level (above regular windows but independent)
// kCGFloatingWindowLevel = 5 (or we can use NSFloatingWindowLevel = 3)
let floating_level: i64 = 3; // NSFloatingWindowLevel
let _: () = msg_send![ns_win, setLevel: floating_level];
}
}
}
set_floating_panel_behavior(&window);

Ok(())
}
Expand Down Expand Up @@ -669,11 +671,8 @@ const EVOLVE_MASCOT_MARGIN: f64 = 24.0;
/// thread). Mirrors `create_preview_indicator_window`: a transparent,
/// borderless, always-on-top floating panel, created hidden and shown on demand.
///
/// IMPORTANT: the raw AppKit panel tweaks below are main-thread-only, which is
/// why this runs in `setup()` rather than lazily from a command worker thread.
/// (An earlier lazy variant wrapped in `run_on_main_thread` never delivered its
/// closure, so the window was never created — this matches the proven preview
/// indicator path instead.)
/// IMPORTANT: `set_floating_panel_behavior` is main-thread-only, which is why
/// this runs in `setup()` rather than lazily from a command worker thread.
pub fn create_evolve_mascot_window<R: Runtime>(app: &AppHandle<R>) -> Result<(), String> {
if app.get_webview_window("evolve-mascot").is_some() {
return Ok(());
Expand Down Expand Up @@ -714,31 +713,8 @@ pub fn create_evolve_mascot_window<R: Runtime>(app: &AppHandle<R>) -> Result<(),
#[cfg(not(target_os = "macos"))]
let _ = &window;

// Disable shadow and set as an independent floating panel (matches the
// preview indicator so it doesn't group/cycle with the main window).
#[cfg(target_os = "macos")]
{
use cocoa::appkit::{NSWindow, NSWindowCollectionBehavior};
use cocoa::base::id;
use objc::msg_send;
use objc::sel;
use objc::sel_impl;

let _ = window.set_shadow(false);

if let Ok(ns_window) = window.ns_window() {
let ns_win = ns_window as id;
unsafe {
ns_win.setCollectionBehavior_(
NSWindowCollectionBehavior::NSWindowCollectionBehaviorCanJoinAllSpaces
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorStationary
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorIgnoresCycle,
);
let floating_level: i64 = 3; // NSFloatingWindowLevel
let _: () = msg_send![ns_win, setLevel: floating_level];
}
}
}
set_floating_panel_behavior(&window);

peek_log!(
"🌀 Created evolve mascot indicator window at ({}, {})",
Expand Down
Loading