fix(ui): Windows 主窗口交还原生 chrome,PR #419 删自定义标题栏的尾巴

PR #419 删了 React 端 WinTitleBar 自定义标题栏,commit message 说"交还
Tauri decorations:true 原生 Win11 标题栏",但漏看 lib.rs 里有一段 Rust
代码在 setup 时**强制**调 set_decorations(false) 把原生 chrome 关掉
(原本是为了让自定义 WinTitleBar 接管)。

结果:v1.3.1-2-beta 上 Windows 主窗口完全无 chrome —— 无法拖动 / 无法
点 X 关闭 / 无法最小化,只能从 tray 退出。用户卡死。

修法:
- lib.rs setup() Windows 分支:移除 set_decorations(false) + 移除
  apply_windows_rounded_frame 调用,保留 apply_mica 给原生 chrome 提供
  磨砂材质
- RunEvent Resized/ScaleFactorChanged 里也移除 apply_windows_rounded_frame
  调用(原生 chrome 自带 resize border + 圆角)
- apply_windows_rounded_frame 函数本身变成 dead code,整段删除(含
  raw_window_handle / Win32 DwmSetWindowAttribute / SetWindowRgn 等 import)

tauri.conf.json 的 decorations:true 配置不动,配合 hiddenTitle:true 让
原生标题栏接管。
This commit is contained in:
baiqing committed 2026-05-12 15:23:47 +08:00
1 parent 5bb1177cb9
commit 830084c426
1 file changed
+5 -107
+5 -107
View File
@@ -145,15 +145,14 @@ pub fn run() {
#[cfg(target_os = "windows")]
{
use window_vibrancy::apply_mica;
// The window starts hidden so Windows native chrome can be disabled before
// the first show; doing this after the native frame is visible is unreliable.
if let Err(e) = main.set_decorations(false) {
log::warn!("[main] disable native decorations failed: {e}");
}
// Windows 走 Tauri decorations:true 原生 Win11 标题栏 / 关闭按钮 /
// 拖动 / 圆角 / resize border。PR #419 删除自定义 WinTitleBar 后,
// 早先的 set_decorations(false) + apply_windows_rounded_frame 链路
// 会让窗口完全无 chrome,用户无法拖/关/最小化(只能 tray 退出)。
// 保留 apply_mica 给原生 chrome 提供磨砂材质。
if let Err(e) = apply_mica(&main, None) {
log::warn!("[main] mica failed: {e}");
}
apply_windows_rounded_frame(&main);
}
// 静默启动开关:prefs.start_minimized = true → 不弹主窗口,
// 用户从菜单栏 / 托盘点击访问。开机自启时尤其有用,避免每次
@@ -339,16 +338,6 @@ pub fn run() {
api.prevent_close();
hide_main_window(app);
}
#[cfg(target_os = "windows")]
if matches!(
event,
tauri::WindowEvent::Resized(_)
| tauri::WindowEvent::ScaleFactorChanged { .. }
) {
if let Some(main) = app.get_webview_window("main") {
apply_windows_rounded_frame(&main);
}
}
}
}
RunEvent::Exit => {
@@ -615,97 +604,6 @@ fn handle_style_tray_menu_event(app: &AppHandle, id: &str) -> bool {
true
}
#[cfg(target_os = "windows")]
fn apply_windows_rounded_frame<R: Runtime>(window: &tauri::WebviewWindow<R>) {
use raw_window_handle::{HasWindowHandle, RawWindowHandle};
use windows::Win32::Foundation::{BOOL, HWND, RECT};
use windows::Win32::Graphics::Dwm::{
DwmSetWindowAttribute, DWMWA_BORDER_COLOR, DWMWA_WINDOW_CORNER_PREFERENCE, DWMWCP_ROUND,
};
use windows::Win32::Graphics::Gdi::{CreateRoundRectRgn, SetWindowRgn, HRGN};
use windows::Win32::UI::WindowsAndMessaging::{
GetWindowLongW, GetWindowRect, SetWindowLongW, SetWindowPos, GWL_STYLE, SWP_FRAMECHANGED,
SWP_NOMOVE, SWP_NOSIZE, SWP_NOZORDER, WS_CAPTION, WS_THICKFRAME,
};
let handle = match window.window_handle().map(|h| h.as_raw()) {
Ok(RawWindowHandle::Win32(handle)) => handle,
Ok(other) => {
log::warn!("[main] unexpected raw window handle for DWM frame: {other:?}");
return;
}
Err(e) => {
log::warn!("[main] read raw window handle failed: {e}");
return;
}
};
let hwnd = HWND(handle.hwnd.get() as *mut core::ffi::c_void);
unsafe {
let style = GetWindowLongW(hwnd, GWL_STYLE);
let desired_style = (style | WS_THICKFRAME.0 as i32) & !(WS_CAPTION.0 as i32);
if style != desired_style {
SetWindowLongW(hwnd, GWL_STYLE, desired_style);
if let Err(e) = SetWindowPos(
hwnd,
HWND::default(),
0,
0,
0,
0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED,
) {
log::warn!("[main] refresh native frame after style update failed: {e}");
}
}
if window.is_maximized().unwrap_or(false) {
let _ = SetWindowRgn(hwnd, HRGN::default(), BOOL(1));
return;
}
let corner_preference = DWMWCP_ROUND;
if let Err(e) = DwmSetWindowAttribute(
hwnd,
DWMWA_WINDOW_CORNER_PREFERENCE,
&corner_preference as *const _ as *const core::ffi::c_void,
std::mem::size_of_val(&corner_preference) as u32,
) {
log::warn!("[main] set DWM rounded corners failed: {e}");
}
// Remove DWM's fallback 1px light border; the React shell draws the visual stroke.
let border_color_none: u32 = 0xFFFFFFFE;
if let Err(e) = DwmSetWindowAttribute(
hwnd,
DWMWA_BORDER_COLOR,
&border_color_none as *const _ as *const core::ffi::c_void,
std::mem::size_of_val(&border_color_none) as u32,
) {
log::warn!("[main] remove DWM border color failed: {e}");
}
let mut rect = RECT::default();
if let Err(e) = GetWindowRect(hwnd, &mut rect) {
log::warn!("[main] read window rect for rounded region failed: {e}");
return;
}
let width = rect.right - rect.left;
let height = rect.bottom - rect.top;
if width <= 0 || height <= 0 {
return;
}
let region = CreateRoundRectRgn(0, 0, width + 1, height + 1, 18, 18);
if region.is_invalid() {
log::warn!("[main] create rounded window region failed");
return;
}
if SetWindowRgn(hwnd, region, BOOL(1)) == 0 {
log::warn!("[main] apply rounded window region failed");
}
}
}
#[tauri::command]
fn restart_app(app: AppHandle) {
// macOS:自动更新会让新装的 .app 带 com.apple.quarantine(无论 Tauri updater