Skip to content
Open
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
78 changes: 78 additions & 0 deletions cardinal/src-tauri/src/window_controls.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::commands::SearchState;
use objc2_app_kit::{NSWindow, NSWindowCollectionBehavior};
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
use tracing::{error, info, warn};

Expand All @@ -10,6 +11,17 @@ pub enum WindowToggle {
}

pub fn activate_window<R: Runtime>(window: &WebviewWindow<R>) {
let window_for_task = window.clone();
if let Err(err) = window.run_on_main_thread(move || {
apply_active_space_behavior(&window_for_task);
show_and_focus_window(&window_for_task);
}) {
error!(?err, "Failed to schedule macOS window activation");
show_and_focus_window(window);
}
}

fn show_and_focus_window<R: Runtime>(window: &WebviewWindow<R>) {
if let Ok(true) = window.is_minimized()
&& let Err(err) = window.unminimize()
{
Expand All @@ -27,6 +39,34 @@ pub fn activate_window<R: Runtime>(window: &WebviewWindow<R>) {
}
}

fn apply_active_space_behavior<R: Runtime>(window: &WebviewWindow<R>) {
let ns_window = match window.ns_window() {
Ok(ns_window) => ns_window,
Err(err) => {
error!(?err, "Failed to get macOS NSWindow");
return;
}
};

if ns_window.is_null() {
warn!("macOS NSWindow pointer is null");
return;
}

// Move the existing window to whichever Space is active before AppKit focuses it.
let ns_window = unsafe { &*ns_window.cast::<NSWindow>() };
let behavior = collection_behavior_for_active_space(ns_window.collectionBehavior());
ns_window.setCollectionBehavior(behavior);
}

fn collection_behavior_for_active_space(
mut behavior: NSWindowCollectionBehavior,
) -> NSWindowCollectionBehavior {
behavior.remove(NSWindowCollectionBehavior::CanJoinAllSpaces);
behavior.insert(NSWindowCollectionBehavior::MoveToActiveSpace);
behavior
}

pub fn hide_window<R: Runtime>(window: &WebviewWindow<R>) -> bool {
if let Err(err) = window.hide() {
error!(?err, "Failed to hide window");
Expand Down Expand Up @@ -108,3 +148,41 @@ pub fn toggle_main_window_impl(app: &AppHandle) {
warn!("Toggle requested but main window is unavailable");
}
}

#[cfg(test)]
mod tests {
use super::*;
use objc2_app_kit::NSWindowCollectionBehavior;

#[test]
fn active_space_behavior_moves_window_without_joining_all_spaces() {
let behavior = collection_behavior_for_active_space(NSWindowCollectionBehavior::Default);

assert!(behavior.contains(NSWindowCollectionBehavior::MoveToActiveSpace));
assert!(!behavior.contains(NSWindowCollectionBehavior::CanJoinAllSpaces));
}

#[test]
fn active_space_behavior_preserves_unrelated_flags() {
let existing =
NSWindowCollectionBehavior::Managed | NSWindowCollectionBehavior::ParticipatesInCycle;

let behavior = collection_behavior_for_active_space(existing);

assert!(behavior.contains(NSWindowCollectionBehavior::Managed));
assert!(behavior.contains(NSWindowCollectionBehavior::ParticipatesInCycle));
assert!(behavior.contains(NSWindowCollectionBehavior::MoveToActiveSpace));
}

#[test]
fn active_space_behavior_clears_all_spaces_visibility() {
let existing =
NSWindowCollectionBehavior::CanJoinAllSpaces | NSWindowCollectionBehavior::Managed;

let behavior = collection_behavior_for_active_space(existing);

assert!(!behavior.contains(NSWindowCollectionBehavior::CanJoinAllSpaces));
assert!(behavior.contains(NSWindowCollectionBehavior::MoveToActiveSpace));
assert!(behavior.contains(NSWindowCollectionBehavior::Managed));
}
}
Loading