Skip to content

fix(desktop): 桌面客户端最大化窗口时光标卡死在 resize 形状prevent cursor stuck at resize shape when window is max… - #6610

Merged
SivanCola merged 2 commits into
esengine:main-v2from
SauronSkywalker:fix/maximized-cursor-stuck_20260717_154500
Jul 19, 2026
Merged

SivanCola merged 2 commits into
esengine:main-v2from
SauronSkywalker:fix/maximized-cursor-stuck_20260717_154500

Conversation

@SauronSkywalker

Copy link
Copy Markdown
Contributor

修复:Wails 桌面客户端最大化窗口时光标卡死在 resize 形状

问题描述

当 Reasonix 桌面客户端(Wails v2.12 frameless 窗口)启动时恢复上次的最大化状态,且用户鼠标停留在屏幕顶部 / 右上角 / 右侧边缘时,光标会变为 resize 形状(n-resize / ne-resize / e-resize),并且卡死在这个形状——移开鼠标后光标不会恢复到默认箭头。最小化再最大化也无法恢复。该问题只在 Reasonix 窗口内出现,其他应用正常。

根因分析

触发条件(两层机制)

第一层 —— 原生 WM_NCHITTEST
Wails frameless 窗口保留了 WS_THICKFRAME 样式以实现前端 resize。最大化后,原生 WM_NCHITTESTDefWindowProc 在窗口边缘 6px 区域返回 HTTOP/HTLEFT/HTRIGHT 等 resize 命中码,Windows 将游标设为 resize 形状。这个操作发生在 JS 层加载之前。

第二层 —— style.cursor = "" 无法触发 SetCursor 回退
useWailsResizeFix hook 的 mousemove handler 检测到鼠标处于边缘时,将 document.documentElement.style.cursor 设为 "n-resize" 等值;当鼠标离开边缘时,清除为 ""。但 style.cursor = ""<html> 上留下 cursor: ;(空值声明),Chromium/WebView2 的空值处理路径不触发 SetCursor 回退,导致原生 resize 游标残留。

启动时卡死概率更高,因为 Vite 的 CSS <link> 异步加载期间 html { cursor: default }styles.css 第 1890 行)可能尚未生效,清空内联样式后无规则兜底。

影响范围

  • Windows 桌面客户端(Wails v2.12 frameless 模式)
  • 仅在窗口最大化状态下触发
  • 非最大化窗口的边缘 resize 拖拽功能不受影响
  • 该问题在 Wails 官方仓库有对应 issue:#1503(V2 Windows/Linux 最大化时显示 resize 光标),至今未合入修复

修复方案

只修改一个文件desktop/frontend/src/lib/useWailsResizeFix.ts

onMouseMove handler 的最开头添加最大化状态检测:

const onMouseMove = (e: MouseEvent) => {
  // 最大化时窗口填满工作区,边缘 6px 与屏幕物理边缘重叠,
  // 跳过边缘检测以免显示误导性的 resize 光标
  if (
    Math.abs(window.outerHeight - window.screen.availHeight) <= 5 &&
    Math.abs(window.outerWidth - window.screen.availWidth) <= 5
  ) {
    if (flags.resizeEdge !== undefined) {
      flags.resizeEdge = undefined;
      document.documentElement.style.removeProperty("cursor");
    }
    return;
  }
  // ... 原有的边缘检测代码 ...
};

为什么这样改

代码 原理
outerHeight/outerWidth ≈ availHeight/availWidth Wails frameless 最大化时客户区精确设为 monitorInfo.rcWork(见 window.go 第 272-283 行),outer 尺寸与工作区尺寸一致。同步检测,无 IPC 延迟,无竞态
≤ 5 px 容差 覆盖 DPI 缩放下可能的 ±1px 舍入误差
removeProperty("cursor") 完全从 style 属性中删除 cursor 声明,强制 WebView2 重新走完整的 CSS 级联计算路径。与 style.cursor = "" 不同,removeProperty 让 WebView2 正确触发 SetCursor 回退
flags.resizeEdge = undefined 清除残留 edge,防止 mousedown 时 Wails 误触原生 resize
return 阻止后续边缘检测执行,style.cursor 从头到尾不被设成 resize 值

不变的部分

  • enableResize = false:继续禁用 Wails 内置 handler(其使用 outerWidth 存在 ZoomFactor 坐标不匹配问题)
  • innerWidth/innerHeight 边缘检测逻辑:修复 ZoomFactor ≠ 1 时的坐标不匹配
  • cleanup 逻辑:卸载时恢复 flagsstyle.cursor

改动量

  • 1 个文件desktop/frontend/src/lib/useWailsResizeFix.ts
  • +8 行,-0 行(实际 +8 行注释 +8 行逻辑)
  • 不涉及 Go 代码、CSS、HTML、第三方库或构建配置

最终效果

场景 修复前 修复后
启动时鼠标在顶部边缘 光标卡死为 resize 形状,无法恢复 光标保持正常箭头
运行中移到顶部边缘再移下 光标可能卡死(取决于 CSS 加载时机) 光标短暂变 resize,移下立即恢复
非最大化窗口拖拽边缘 正常 正常(不受影响)
最小化再最大化 光标仍可能卡死 光标始终正常

本地验证(windows11)

  • pnpm build(前端) ✅ 4295 modules transformed, 8.75s
  • tsx 前端单元测试(8 项) ✅ 8 passed, 0 failed
  • wails build(桌面客户端) ✅ build/bin/reasonix-desktop.exe 生成成功
  • 实际运行测试 ✅ 启动时鼠标在顶部不卡死,运行时边缘交互正常

…imized

When the Wails frameless window is restored maximized, the 6px edge-detection
zone in useWailsResizeFix coincides with the physical screen edges. Moving
the mouse to the top/right/top-right corner sets style.cursor to a resize
value that WebView2 never reliably reverts via style.cursor = "", leaving
the cursor stuck in resize shape until the app is restarted.

The fix adds a synchronous check at the top of the mousemove handler:
when outerDimensions ≈ screen.availDimensions (within 5px tolerance) the
window is maximised, so edge detection is skipped entirely and any stale
resize edge is cleared via removeProperty("cursor").

Fixes wailsapp/wails#1503 (same bug, project-specific manifestation).
@github-actions github-actions Bot added v2 Go rewrite (1.x) — main-v2 branch, active development desktop Wails desktop app (desktop/**) labels Jul 17, 2026
Problem:
Maximized Windows desktop windows could retain a resize cursor, while near-full normal windows could be misclassified as maximized by the size heuristic.

Root cause:
Cursor recovery was gated on Wails resizeEdge state, and maximization was inferred from outer and screen dimensions instead of the native window state.

Fix:
Share the native maximized state with the Windows controls, normalize stale resize cursors, preserve the remembered default cursor, and skip edge detection only for confirmed maximized windows. Add focused startup, transition, and near-full-size regression coverage.

Verification:
- pnpm exec tsx src/__tests__/wails-resize-fix.test.tsx
- pnpm exec tsx src/__tests__/app-chrome-tabs.test.ts
- pnpm run typecheck
- pnpm test
- pnpm run build
- git diff --check

Co-authored-by: SivanCola <32437197+SivanCola@users.noreply.github.com>
@SivanCola
SivanCola merged commit a46fc6f into esengine:main-v2 Jul 19, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

desktop Wails desktop app (desktop/**) v2 Go rewrite (1.x) — main-v2 branch, active development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants