Skip to content
Draft
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
3 changes: 3 additions & 0 deletions templates/clips/desktop/src-tauri/src/clips/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,9 @@ pub async fn show_bubble(app: AppHandle) -> Result<(), String> {
// `getDisplayMedia`, which matches the other Clips chrome (popover,
// toolbar, countdown) but NOT what users want for the camera bubble.
let _ = win.show();
// canJoinAllSpaces: bubble follows the user across every Mission Control
// space and every monitor — not just the one where the session started.
crate::util::set_window_can_join_all_spaces(&win);
dlog!("[clips-tray] bubble shown at ({},{}) size {}", x, y, size);
Ok(())
}
Expand Down
8 changes: 7 additions & 1 deletion templates/clips/desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use state::{
DictationActive, DictationEnabled, LastTranscript, MeetingActive, PopoverShownAt,
RecordingActive, TrayAnchor, TrayMeetings, VoiceWakePopover,
};
use util::{is_recording_active, set_capture_included};
use util::{is_recording_active, set_capture_included, set_window_can_join_all_spaces};

// Embedded fallback icon — a tiny 16x16 solid purple PNG so the binary always
// has *something* to display even if `icons/tray.png` is missing on disk. The
Expand Down Expand Up @@ -185,6 +185,12 @@ pub fn run() {
// popover boots.
meetings_watcher::spawn_watcher(app.handle().clone());

// Ensure the popover appears above every app on every Mission
// Control space so it's reachable before the user starts recording.
if let Some(window) = app.get_webview_window("popover") {
set_window_can_join_all_spaces(&window);
}

// Hide the popover on blur so it feels like a real menu-bar popover.
// The 250ms guard is the important bit — during the tray-click
// itself macOS briefly steals focus from the popover, which would
Expand Down
39 changes: 39 additions & 0 deletions templates/clips/desktop/src-tauri/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,45 @@ pub fn show_without_activation(window: &WebviewWindow) {
}
}

/// Set `NSWindowCollectionBehaviorCanJoinAllSpaces` so the window follows the
/// user across every Mission Control space and appears on any monitor — not
/// just the one where it was first shown.
#[cfg(target_os = "macos")]
pub fn set_window_can_join_all_spaces(window: &WebviewWindow) {
let win = window.clone();
if let Err(err) = win.clone().run_on_main_thread(move || {
let label = win.label().to_string();
let ns_window_ptr = match win.ns_window() {
Ok(p) => p,
Err(err) => {
eprintln!(
"[clips-tray] set_window_can_join_all_spaces({label}): ns_window() failed: {err}"
);
return;
}
};
if ns_window_ptr.is_null() {
return;
}
unsafe {
let obj = ns_window_ptr as *mut objc2::runtime::AnyObject;
// Read the current behavior so we don't discard flags Tauri
// already set (e.g. NSWindowCollectionBehaviorManaged = 4).
// NSWindowCollectionBehaviorCanJoinAllSpaces = 1 << 0 = 1.
let current: usize = objc2::msg_send![&*obj, collectionBehavior];
let _: () = objc2::msg_send![&*obj, setCollectionBehavior: current | 1usize];
}
dlog!("[clips-tray] set_window_can_join_all_spaces({label}): applied");
}) {
eprintln!(
"[clips-tray] set_window_can_join_all_spaces: run_on_main_thread failed: {err}"
);
}
}

#[cfg(not(target_os = "macos"))]
pub fn set_window_can_join_all_spaces(_window: &WebviewWindow) {}

#[cfg(not(target_os = "macos"))]
pub fn show_without_activation(window: &WebviewWindow) {
// On non-macOS we just fall back to the standard show. Focus stealing
Expand Down
9 changes: 0 additions & 9 deletions templates/clips/desktop/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -2049,9 +2049,6 @@ body[data-clips-route]:not([data-clips-route="popover"]) #root {
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow:
0 10px 30px rgba(0, 0, 0, 0.35),
0 2px 6px rgba(0, 0, 0, 0.3);
color: white;
cursor: grab;
}
Expand Down Expand Up @@ -2176,7 +2173,6 @@ body[data-clips-route]:not([data-clips-route="popover"]) #root {
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.06);
box-shadow: 0 14px 40px rgba(0, 0, 0, 0.45);
user-select: none;
-webkit-user-select: none;
cursor: grab;
Expand Down Expand Up @@ -2312,9 +2308,6 @@ body[data-clips-route]:not([data-clips-route="popover"]) #root {
border-radius: 50%;
overflow: hidden;
background: #000;
/* Drop shadow only — no brand ring. The bubble is the user's face;
a bright purple halo competes with what matters visually. */
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.45);
/* Position context for absolute children (close X, canvas). */
position: relative;
/* Cursor hint — the drag region is here. */
Expand Down Expand Up @@ -2407,7 +2400,6 @@ body[data-clips-route]:not([data-clips-route="popover"]) #root {
z-index: 3;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}

.bubble-close:hover {
Expand All @@ -2433,7 +2425,6 @@ body[data-clips-route]:not([data-clips-route="popover"]) #root {
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
z-index: 3;
/* Cursor over the pill should not be a grab cursor (we're not dragging
the bubble when clicking its controls). */
Expand Down
Loading