最近使用tauri框架进行软件开发,碰上了不少的坑。tauri还算是一个比较新的框架,很多问题遇到了都找不到解决办法,遂记录下来方便自己,也希望遇到同样问题的同学可以找到问题的答案。
tauri使用window-vibrancy在Windows 10下拖动窗口卡顿问题
这个问题在window-vibrancy作者的README中也提示了。
Function | Supported platforms | Notes |
---|---|---|
apply_blur &clear_blur |
Windows 7/10/11 (22H1 only) | Bad performance when resizing/dragging the window on Windows 11 build 22621+. |
apply_acrylic &clear_acrylic |
Windows 10/11 | Bad performance when resizing/dragging the window on Windows 10 v1903+ and Windows 11 build 22000. |
apply_mica &clear_mica |
Windows 11 | |
apply_vibrancy &clear_vibrancy |
macOS 10.10 and newer |
我的解决办法是检测Windows的版本,如果是有性能问题的版本,则在拖动窗口时先清除acrylic特效,然后拖动结束后再apply回来。
javascript
// 添加检查 Windows 版本的函数
const hasAcrylicPerformanceIssueVersion = () => {
const plat = platform();
const ver = version();
if (plat !== "windows") return false;
// 解析版本号
const [major, minor, build] = ver.split(".").map(Number);
// https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information
// Windows 10 v1903+ (build 18362+) 到 Windows 10 最后一个版本
const isWin10WithIssue =
major === 10 && minor === 0 && build >= 18362 && build < 22000;
// Windows 11 build 22000
const isWin11WithIssue = major === 10 && minor === 0 && build === 22000;
return isWin10WithIssue || isWin11WithIssue;
};
上面用到的是tauri的os-infoOS Infomation | Tauri插件,获取Windows的版本号,检查是否为有性能问题的版本。
这样就出现了第二个问题,data-tauri-drag-region会导致鼠标的事件被吞[bug] Dragging window causes focus toggling, plus eats mouse event(s) · Issue #10767 · tauri-apps/tauri,就导致我们监听不到鼠标释放的事件,不知道什么时候拖动结束,将acrylic特效清除后就加不回来了。
最后找了很久,想到了一个曲线救国的办法,利用GetAsyncKeyState全局检测鼠标左键。
解决方案
首先监听onMouseDown事件,点击后就清除acrylic特效,随后利用Windows API,使用GetAsyncKeyState检测鼠标左键是否已经释放,如果释放,则调用applyAcrylic将窗口特效添加回来。虽然谈不上优雅的解决方案,但是至少没有特别大的问题。
javascript
const handleDrag = async (e: React.MouseEvent) => {
if (e.button === 0) {
const hasPerformanceIssue = hasAcrylicPerformanceIssueVersion();
if (hasPerformanceIssue) {
dispatch(setWindowVibrancy(false));
clearWindowAcrylic();
}
const appWindow = getCurrentWindow();
await appWindow.startDragging();
if (hasPerformanceIssue) {
// 每100ms检查一次鼠标左键状态,如果为放开状态,则恢复窗口模糊
const interval = setInterval(() => {
getSwapButtonState().then((state) => {
if (state) {
getAsyncKeyState(0x02).then((mouseState) => {
if (mouseState >= 0) {
restoreWindowVibrancy();
// 停止检查
clearInterval(interval);
}
});
} else {
getAsyncKeyState(0x01).then((mouseState) => {
if (mouseState >= 0) {
restoreWindowVibrancy();
// 停止检查
clearInterval(interval);
}
});
}
});
}, 100);
}
}
};
上面就是修改之后的拖动监听函数。这里调用了两个Windows API
GetSystemMetrics检查鼠标调换
getSwapButtonState()实际上调用的是GetSystemMetrics function (winuser.h) - Win32 apps | Microsoft Learn,首先需要检测系统是否调换了鼠标左右键,在GetAsyncKeyState function (winuser.h) - Win32 apps | Microsoft Learn的Remarks中也提到了,GetAsyncKeyState直接检查鼠标的物理按键,而不是逻辑按键,因此如果系统调换了左右键,GetAsyncKeyState获取的结果就对不上了。
GetAsyncKeyState检查左键是否释放
GetAsyncKeyState function (winuser.h) - Win32 apps | Microsoft Learn接受一个参数,案件的virtual key,具体的key code在这里Virtual-Key Codes (Winuser.h) - Win32 apps | Microsoft Learn。0x01代表鼠标左键,0x02代表鼠标右键
因此上面的代码就是检查鼠标是否调换,如果调换了就调用GetAsyncKeyState(0x02),否则调用GetAsyncKeyState(0x01)进行检查。
GetAsyncKeyState的返回结果官方也做了较为详细的说明,返回类型为SHORT
vbnet
If the most significant bit is set, the key is down, and if the least significant bit is set, the key was pressed after the previous call to GetAsyncKeyState
如果最高位被设置了,则该键被按下,如果最低位被设置了,则该键从上次调用GetAsyncKeyState后被按下了。
我们在这里需要解决的是获取键是否被释放,以便我们恢复窗口的acrylic特效。根据上面的介绍就可以知道,如果最高位被设置了就是被按下,则最高位没被设置就是处于释放状态。
由于返回值类型为SHORT,是一个有符号整数,因此最高位为符号位,所以我们只需要判断返回值是否大于等于0,就可以知道按键是否处于释放状态。
前端调用Windows API
由于tauri的后端是rust,所以这里我使用的是windows - crates.io: Rust Package Registry,只需要在后端写上调用Windows API的代码,然后使用tauri的#[tauri::command]宏,就可以轻松调用后端代码了。
rust
pub fn get_async_key_state(key: i32) -> Result<i16, Box<dyn std::error::Error>> {
//使用windows crate的GetAsyncKeyState函数
let state = unsafe { windows::Win32::UI::Input::KeyboardAndMouse::GetAsyncKeyState(key) };
Ok(state)
}
然后去注册这个方法成为commands
rust
#[tauri::command]
pub fn get_async_key_state(key: i32) -> i16 {
match hotkey_listener::get_async_key_state(key) {
Ok(ret) => ret,
Err(e) => {
eprintln!("Failed to get async key state, Error: {}", e.to_string());
return 0;
}
}
}
在main函数的invoke_handler中注册这个get_async_key_state,之后就可以在javascript中使用invoke调用了。
javascript
export function getAsyncKeyState(key: number) {
return invoke<any>("get_async_key_state", {
key,
});
}
至此,我们就已经解决了这个问题。首先通过onMouseDown检测鼠标按下,如果是受影响的Windows版本,则清除窗口的特效,然后使用setInterval()开始循环调用GetAsyncKeyState(),直到用户停止拖动,释放鼠标左键的那一刻,GetAsyncKeyState()的返回值由负数变为正数,恢复窗口的特效,退出setInterval()。