Skip to content

Commit 2de0af2

Browse files
committed
fix: use cfg for macOS-only title_bar_style API
1 parent b8f76e7 commit 2de0af2

2 files changed

Lines changed: 77 additions & 47 deletions

File tree

src-tauri/src/lib.rs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2798,6 +2798,7 @@ async fn test_zenmux_connection(base_url: String, auth_token: String, model: Str
27982798
#[cfg(target_os = "macos")]
27992799
fn setup_float_window_macos(app: &tauri::App) {
28002800
use tauri::Manager;
2801+
use objc::*;
28012802

28022803
if let Some(window) = app.get_webview_window("float") {
28032804
// 获取原生 NSWindow 句柄
@@ -2817,10 +2818,18 @@ fn setup_float_window_macos(app: &tauri::App) {
28172818
// 设置窗口级别为悬浮面板 (NSFloatingWindowLevel = 3)
28182819
ns_win.setLevel_(3);
28192820

2821+
// 关键:添加 NSWindowStyleMaskNonactivatingPanel 样式
2822+
// 这让窗口可以成为 key window 但不成为 main window
2823+
// 从而让 WebKit 正常更新光标
2824+
// NSWindowStyleMaskNonactivatingPanel = 1 << 7 = 128
2825+
let current_style: u64 = msg_send![ns_win, styleMask];
2826+
let new_style = current_style | (1 << 7); // NSWindowStyleMaskNonactivatingPanel
2827+
let _: () = msg_send![ns_win, setStyleMask: new_style];
2828+
28202829
// 关键:忽略鼠标事件不会让窗口获得焦点
28212830
ns_win.setIgnoresMouseEvents_(cocoa::base::NO);
28222831

2823-
println!("[DEBUG] Float window macOS properties configured");
2832+
println!("[DEBUG] Float window macOS properties configured with NonactivatingPanel style");
28242833
}
28252834
}
28262835
}
@@ -3116,12 +3125,17 @@ pub fn run() {
31163125
let _ = window.set_focus();
31173126
} else {
31183127
// Recreate main window
3119-
let _ = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
3128+
#[cfg(target_os = "macos")]
3129+
let builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
31203130
.title("Lovcode")
31213131
.inner_size(800.0, 600.0)
31223132
.title_bar_style(tauri::TitleBarStyle::Overlay)
3123-
.hidden_title(true)
3124-
.build();
3133+
.hidden_title(true);
3134+
#[cfg(not(target_os = "macos"))]
3135+
let builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
3136+
.title("Lovcode")
3137+
.inner_size(800.0, 600.0);
3138+
let _ = builder.build();
31253139
}
31263140
}
31273141
"show_float" => {
@@ -3147,6 +3161,7 @@ pub fn run() {
31473161
// Apply macOS specific settings
31483162
#[cfg(target_os = "macos")]
31493163
{
3164+
use objc::*;
31503165
if let Ok(ns_window) = window.ns_window() {
31513166
unsafe {
31523167
let ns_win: id = ns_window as id;
@@ -3156,6 +3171,10 @@ pub fn run() {
31563171
| NSWindowCollectionBehavior::NSWindowCollectionBehaviorIgnoresCycle;
31573172
ns_win.setCollectionBehavior_(behavior);
31583173
ns_win.setLevel_(3);
3174+
// 添加 NonactivatingPanel 样式让光标正常更新
3175+
let current_style: u64 = msg_send![ns_win, styleMask];
3176+
let new_style = current_style | (1 << 7);
3177+
let _: () = msg_send![ns_win, setStyleMask: new_style];
31593178
ns_win.setIgnoresMouseEvents_(cocoa::base::NO);
31603179
}
31613180
}

src/components/FloatWindow.tsx

Lines changed: 54 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,30 @@ export function FloatWindow() {
6565
const [isExpanded, setIsExpanded] = useState(savedState.isExpanded ?? false);
6666
const [expandDirection, setExpandDirection] = useState<"left" | "right">(savedState.expandDirection ?? "right");
6767
const [snapSide, setSnapSide] = useState<"left" | "right" | null>(savedState.snapSide ?? null);
68-
const [hoveredId, setHoveredId] = useState<string | null>(null);
6968
const isDraggingRef = useRef(false);
7069
const initializedRef = useRef(false);
7170

71+
// 修复无焦点时光标不显示手型的问题
72+
// WebKit 在非活动窗口中不会自动更新 CSS cursor,需要手动强制设置
73+
useEffect(() => {
74+
const handleMouseMove = (e: MouseEvent) => {
75+
const target = e.target as HTMLElement;
76+
// 检查元素或其父元素是否有 cursor-pointer 类或是可点击的
77+
const isClickable = target.closest('.cursor-pointer, button, [role="button"], a');
78+
document.body.style.cursor = isClickable ? 'pointer' : 'default';
79+
};
80+
81+
const handleMouseLeave = () => {
82+
document.body.style.cursor = 'default';
83+
};
84+
85+
document.addEventListener('mousemove', handleMouseMove);
86+
document.addEventListener('mouseleave', handleMouseLeave);
87+
return () => {
88+
document.removeEventListener('mousemove', handleMouseMove);
89+
document.removeEventListener('mouseleave', handleMouseLeave);
90+
};
91+
}, []);
7292

7393
// 磁吸到边缘
7494
const snapToEdge = async () => {
@@ -411,49 +431,40 @@ export function FloatWindow() {
411431

412432
{/* Items list */}
413433
<div className="space-y-2 max-h-56 overflow-y-auto">
414-
{items.map((item, index) => {
415-
const isHovered = hoveredId === item.id;
416-
return (
417-
<motion.div
418-
key={item.id}
419-
initial={{ opacity: 0, y: 10 }}
420-
animate={{ opacity: 1, y: 0 }}
421-
transition={{ delay: index * 0.05 }}
422-
onClick={() => handleItemClick(item)}
423-
onMouseEnter={() => setHoveredId(item.id)}
424-
onMouseLeave={() => setHoveredId(null)}
425-
className={`flex items-center gap-2 p-2 rounded-lg transition-colors cursor-pointer ${
426-
isHovered ? "bg-white/20" : "bg-white/10"
427-
}`}
434+
{items.map((item, index) => (
435+
<motion.div
436+
key={item.id}
437+
initial={{ opacity: 0, y: 10 }}
438+
animate={{ opacity: 1, y: 0 }}
439+
transition={{ delay: index * 0.05 }}
440+
onClick={() => handleItemClick(item)}
441+
className="group flex items-center gap-2 p-2 rounded-lg transition-colors cursor-pointer bg-white/10 hover:bg-white/20"
442+
>
443+
{/* tmux indicator */}
444+
{item.tmux_session && (
445+
<Terminal className="w-4 h-4 shrink-0 opacity-70" />
446+
)}
447+
<div className="flex-1 min-w-0">
448+
<p className="text-sm font-medium truncate">{item.title}</p>
449+
<p className="text-xs opacity-70 truncate">
450+
{item.tmux_session && (
451+
<span>{item.tmux_session}:{item.tmux_window}.{item.tmux_pane} · </span>
452+
)}
453+
{formatTime(item.timestamp)}
454+
</p>
455+
</div>
456+
<motion.button
457+
whileTap={{ scale: 0.9 }}
458+
onClick={(e) => {
459+
e.stopPropagation();
460+
handleDismiss(item.id);
461+
}}
462+
className="p-1 rounded transition-opacity opacity-0 group-hover:opacity-100 group-hover:bg-white/10"
428463
>
429-
{/* tmux indicator */}
430-
{item.tmux_session && (
431-
<Terminal className="w-4 h-4 shrink-0 opacity-70" />
432-
)}
433-
<div className="flex-1 min-w-0">
434-
<p className="text-sm font-medium truncate">{item.title}</p>
435-
<p className="text-xs opacity-70 truncate">
436-
{item.tmux_session && (
437-
<span>{item.tmux_session}:{item.tmux_window}.{item.tmux_pane} · </span>
438-
)}
439-
{formatTime(item.timestamp)}
440-
</p>
441-
</div>
442-
<motion.button
443-
whileTap={{ scale: 0.9 }}
444-
onClick={(e) => {
445-
e.stopPropagation();
446-
handleDismiss(item.id);
447-
}}
448-
className={`p-1 rounded transition-opacity ${
449-
isHovered ? "opacity-100 bg-white/10" : "opacity-0"
450-
}`}
451-
>
452-
<X className="w-3.5 h-3.5" />
453-
</motion.button>
454-
</motion.div>
455-
);
456-
})}
464+
<X className="w-3.5 h-3.5" />
465+
</motion.button>
466+
</motion.div>
467+
))}
457468
</div>
458469

459470
{items.length === 0 && (

0 commit comments

Comments
 (0)