)
Serial Studio Spec 0004 解读CSD 窗口原生 OS/WM 阴影改造Native Window Shadows【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio本篇技术指南围绕 Serial Studio 的规格文档doc/claude/specs/0004-native-csd-shadow/spec.md展开系统阐述该项目如何将 Windows 10 与 Linux 上客户端自绘CSDClient-Side Decorations窗口的自绘阴影 几何补偿方案替换为操作系统/窗口管理器WM绘制的原生阴影从而根治窗口几何、贴边、缩放手势与弹出层等一系列缺陷。读完本文你将理解 CSD 阴影问题的根因、R1–R9 九项需求与 AC1–AC9 验收标准并结合core/Ui/Platform/下的真实实现掌握 Qt 无边框窗口对接 DWM 阴影、X11/Wayland 降级策略的落地方式。背景CSD 窗口与自绘阴影的代价Serial Studio 在多数平台上使用 Qt Quick 构建界面但窗口装饰标题栏、最小化/最大化/关闭按钮并不总是交给操作系统绘制。按照 spec.md 的描述在Windows 10 与 Linux上应用自己绘制窗口边框CSD 模式同时也自行绘制下拉阴影。实现方式是每个 CSD 窗口的实际尺寸大于其可见内容——窗口四周环绕一圈不可见的阴影带于是所有涉及窗口几何的代码都必须把这圈隐形边距补偿回去。这种补偿逻辑是持续的 bug 来源。从源码看CSD 装饰器确实在 CSD.cpp 中被大量几何代码环绕updateBorderGeometry()、updateContentContainerGeometry()、updateTitleBarGeometry()、updateMinimumSize()等函数需要精确协调窗口四边、标题栏高度与内容容器的尺寸任何一处遗漏都会导致布局错位。四类高频缺陷Motivation规格文档明确记录了维护者在日常使用中反复遇到的四个缺陷类别对话框尺寸计算错误对话框的尺寸计算必须把隐形边距加回来并非总能算对导致弹窗尺寸异常。窗口无法贴边窗口移动到屏幕边缘时隐形阴影带先碰到边缘可见窗口与屏幕之间会留下一道空隙。缩放手势错位缩放手柄resize handle激活在阴影带上而不是可见边框上光标在看似空白的地方变化、拖动也能生效。弹出层进入死区下拉框/菜单弹窗可以延伸进甚至超出隐形阴影带在那里被裁剪或不可点击。此外自绘阴影在每次窗口 chrome 重绘时都会消耗渲染时间。规格明确表态合成器compositor本身就知道如何绘制窗口阴影把这项工作委托给系统可以整体消除这一缺陷类别而不是逐个修补症状。目标与非目标Goals / Non-Goals目标在任何平台上CSD 窗口的几何尺寸都与可见内容完全一致——四周不再有任何隐形边距。在 OS/合成器能对无边框窗口绘制阴影的地方阴影由系统绘制且外观与行为激活/非激活状态、堆叠、贴靠与其它原生窗口一致。在没有该机制的地方窗口降级为干净的单像素细边框、无阴影。自绘阴影及其几何补偿代码被彻底删除不保留为回退方案。现有的自定义 CSD 标题栏颜色、最小化/最大化/关闭控件、拖动/双击行为保持不变。非目标不改变 Windows 11 与 macOS 的窗口装饰行为二者本就使用原生装饰。不在 Wayland 上引入服务端装饰自定义 CSD 标题栏继续保留Wayland 窗口只是没有阴影加上回退边框合成器绘制完整装饰不在范围内。不新增用户可见设置项现有的 CSD shadow 开关被移除而非泛化阴影的有无变成平台决策。不重新设计 CSD 标题栏的视觉或窗口控件。不追求复刻旧自绘阴影的外观原生阴影看起来像平台本身而不是旧 chrome。需求清单Requirements R1–R9编号需求要点R1True-size windows所有平台上系统报告与使用的窗口几何位置、尺寸、最小尺寸与可见窗口完全一致R2Dialog sizing对话框按内容请求的尺寸打开不再因装饰边距出现多余的空白带或内容被裁剪R3Edge travelCSD 窗口可移动并贴靠到任意屏幕边缘可见边框紧贴边缘无空隙R4Resize hit zones缩放光标与拖动仅且仅在可见边框区域激活可见窗口之外不存在活动区R5Popup integrity下拉框与菜单弹窗完整可见、完整可点击任何弹窗区域都不会落入死区R6Native shadow where supported在支持合成器绘制无装饰窗口阴影的平台最低要求Windows 10上显示系统阴影自动检测无需用户配置R7Graceful degradation在无此机制的系统Wayland以及无此机制的 X11 WM/桌面上窗口显示细对比边框且无阴影R8Setting removalSettings 中的 CSD shadow 开关消失残留的旧偏好被无害忽略R9No regression elsewhereWindows 11、macOS 窗口以及非 CSD原生装饰模式的外观与行为与之前完全一致验收标准Acceptance Criteria AC1–AC9规格文档给出的验收标准全部标记为已完成[x]它们是可复现的验证步骤AC1R1/R2在 Windows 10 与 Linux 上打开主窗口可达的每个对话框Settings、About、Donate、Project Editor 对话框、CSV player、examples browser每个都按内容尺寸打开无空白边带、无内容裁剪。AC2R3在 Windows 10 与 Linux 上把主窗口与对话框拖向四个屏幕边缘可见边框触边OS snap/aero-snap 手势仍正常。AC3R4悬停 CSD 窗口的每条边与每个角缩放光标恰好出现在可见边框处且可缩放光标不会在可见窗口之外变化。AC4R5在靠近窗口底部/右边缘处打开长下拉框如 Setup 中的驱动选择器、Settings 中的主题选择器每个条目都渲染且可点击。AC5R6/R7Windows 10 上窗口具有与原生应用视觉一致的 DWM 阴影KDE/KWin X11 上若 WM 机制可用则绘制阴影否则出现细边框回退。AC6R5/R7GNOME Wayland 与 KDE Wayland 上窗口显示细边框回退移动/缩放/贴靠正确弹窗行为正常。AC7R8Settings 不再显示阴影开关用包含旧键的既有settings.ini启动既不崩溃也不改变行为。AC8R9Windows 11 与 macOS 冒烟测试装饰行为、标题颜色、退出/最小化/最大化与当前版本一致。AC9pytest tests/integration/ -v在至少一个 CSD 平台上对运行中的应用通过确认无 API/窗口管理器交互回归。约束与不变量Constraints Invariants零新增第三方依赖平台检测只能使用 Qt 与操作系统已提供的能力。自定义 CSD 标题栏在 Windows 10 与 Linux 上仍然是拖动与窗口控件的表面其高度、颜色与行为不变。从 CSD.h 可以看到标题栏高度常量TitleBarHeight 32、TitleBarHeightMaximized 28以及完整的Titlebar类。运行时平台/WM 检测必须在异常环境裸 X11 WM、SSH X-forwarding、虚拟机中保持安全未知环境一律走降级路径绝不崩溃、绝不出现隐形窗口。运行时创建的既有窗口外部 widget 窗口、对话框与主窗口享受同等对待不维护第二套几何模型。启动不得回退检测不能阻塞窗口呈现。旧版本持久化的窗口几何以带阴影放大的尺寸保存必须恢复到合理状态不能每次启动都按旧边距增长/缩小。源码级实现印证CSD 装饰器无边框窗口的三件套在 CSD.cpp 中CSD::Window构造函数对每个目标窗口执行setFlags(flags | Qt::FramelessWindowHint)并安装事件过滤器随后依次建立三部分装饰setupTitleBar()创建Titlebar一个QQuickPaintedItem负责绘制标题、窗口图标与最小化/最大化/关闭按钮并处理拖拽startSystemMove()、双击最大化、按钮悬停/按下态在 Windows 上还支持右键唤出原生 Win32 系统菜单showNativeSystemMenu()通过GetSystemMenu/TrackPopupMenu/WM_SYSCOMMAND恢复无边框窗口丢失的右键菜单行为。setupBorder()用一段内嵌 QML 生成四条 1px 边线#73666666以setZ(1000000)浮在内容之上——这就是 R7 中的细对比边框回退外观。内容容器与几何同步updateContentContainerGeometry()/updateTitleBarGeometry()/updateMinimumSize()在窗口状态变化最大化/全屏时重新布局最大化时边框隐藏fillScreen判定标题栏高度切换到TitleBarHeightMaximized。规格中移除几何补偿、窗口尺寸等于可见内容的目标正对应这套代码里窗口本身不再预留阴影边距的设计——装饰只包含标题栏与 1px 边框均为可见内容。Windows 10 原生阴影DWM 双管齐下真正实现OS 绘制阴影的核心在 NativeWindow_CSD.cpp恢复可缩放帧样式enableNativeShadow()通过SetWindowLongPtr(hwnd, GWL_STYLE, ...)为无边框窗口补回WS_THICKFRAME | WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX让窗口仍然具备可缩放、可最小化/最大化的原生语义这也是 aero-snap 仍能工作的原因。DwmExtendFrameIntoClientArea(hwnd, {0, 0, 1, 0})把窗口底部 1px 的边距交给 DWM 绘制让DWM 继续渲染下拉阴影同时不引入原生标题栏。CsdNativeShadowFilterQAbstractNativeEventFilter拦截WM_NCCALCSIZE当消息参数wParam TRUE时把客户区矩形直接设为完整窗口矩形最大化时按SM_CXSIZEFRAME/SM_CYSIZEFRAMESM_CXPADDEDBORDER内缩从而抹掉 Win32 原生边框视觉——阴影由 DWM 保留原生 frame 却不显示。过滤器通过winId()实时比对 HWND避免平台窗口销毁/重建后被回收的 HWND 值误判。这套组合完美对应 R6Windows 10 原生阴影与 AC5DWM 阴影与原生应用视觉一致。值得注意的细节是isWindows11()的存在Windows 11 本就用原生装饰见 NativeWindow_macOS.mm 与 NativeWindow.h 的平台分支因此这套 DWM 技巧只作用于 Windows 10 及需要 CSD 的 Linux 会话。降级路径X11/Wayland 的细边框回退对于 WaylandGNOME/KDE以及没有合成阴影机制的 X11 WM规格要求窗口降级为细对比边框 无阴影R7。这正是上文setupBorder()生成的 1px 四边线的作用它不依赖任何合成器特性仅使用 Qt Quick 场景图即可绘制天然满足未知环境走降级路径、永不崩溃的约束。设置项移除与兼容R8 要求 Settings 中的 CSD shadow 开关消失且旧settings.ini中的残留键被无害忽略。搜索 Settings.qml 已无任何shadow相关控件旧偏好键不再被读取——符合移除而非泛化的决策也不违反 AC7 的启动兼容性要求。回归验证AC9 对应的pytest tests/integration/位于 tests/integration用于在运行中的应用上验证无 API/窗口管理器交互回归规格同时要求 Windows 11 与 macOS 做装饰行为冒烟测试AC8因为这两个平台不进入 CSD 路径必须确认零改动。遗留问题与结论规格文档的 Open Questions 一栏为空并注明回退外观细边框、无阴影、设置项移除与 Wayland 无阴影立场已于 2026-07-07 与维护者共同敲定。九项需求与九条验收标准全部闭环意味着自绘阴影及其几何补偿已从代码中移除CSD 窗口的几何即可见内容阴影完全交给平台——Windows 10 上由 DWM 绘制Linux 桌面由合成器绘制若支持其余环境则退化为干净的单像素边框。对本项目后续开发者的启示阅读 spec.md 理解设计意图阅读 CSD.cpp 与 NativeWindow_CSD.cpp 掌握实现细节任何新的无边框窗口需求都应遵循真尺寸、原生阴影、优雅降级这三个不变量。【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考