news 2026/10/23 19:58:18

MarkerOn 透明覆盖层的秘密:轻量屏幕标注窗口 win32 与 macOS 实现完整详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MarkerOn 透明覆盖层的秘密:轻量屏幕标注窗口 win32 与 macOS 实现完整详解

【免费下载链接】markeron

Lightweight (~1.5 MB) open-source screen annotation with click-through mode and keyboard-first shortcuts. For demos, teaching, meetings & screen recording. Windows, macOS.

项目地址:https://gitcode.com/gh_mirrors/mar/markeron
点击查看免费下载

MarkerOn 是一款仅约 1.5 MB 的开源屏幕标注工具,按下 Ctrl+Shift+D 即可在整个桌面上绘画、高亮、做标记。它的核心是一块覆盖全屏的透明覆盖层(Overlay)窗口——既不能遮挡鼠标操作,又不能出现恼人的黑边或白框。这篇文章带你拆解 MarkerOn 是如何在 win32 与 macOS 上实现这块透明覆盖层的,涉及 DWM 透明、子窗口堆叠、光标禁锢等完整窗口技术细节。

屏幕标注覆盖层的三大难题

做一个"画在所有应用之上"的标注层,表面上就是建一个无边框透明窗口,实际上要同时解决三个问题:

  1. 真透明:窗口背景必须全透明,且不能在使用中"变黑变白"——WebView 内核(WebView2 / WKWebView)的渲染器会因空闲、GPU 回收、DPI 变化等原因悄悄丢掉透明状态;
  2. 置顶与点透:窗口必须永远浮在最上层,又要能随时把鼠标"放穿"给下面的应用(click-through 穿透模式);
  3. 多屏与边缘细节:覆盖层要精确贴合当前鼠标所在的显示器,还不能惊动任务栏、不能破坏浏览器全屏。

MarkerOn 的后端用 Rust + Tauri 实现,平台差异全部隔离在独立模块里:

模块职责
src-tauri/src/overlay.rs覆盖层/工具栏窗口生命周期与三态状态机(跨平台)
src-tauri/src/win32.rsWindows 平台:DWM 透明、置顶、全屏判定
src-tauri/src/macos.rsmacOS 平台:AppKit 子窗口、键窗口管理
src-tauri/src/macos_cursor.rsmacOS 光标禁锢(轮询 + 回弹)
src-tauri/src/video_compat.rsWindows 录屏/浏览器全屏兼容
src-tauri/src/monitor.rs显示器几何、光标边界(跨平台)

总体架构:两个窗口 + 三态状态机

覆盖层与工具栏在 tauri.conf.json 中声明:overlay窗口设置transparent: true、decorations: false、alwaysOnTop: true、skipTaskbar: true,初始隐藏。

后端用一个简单的状态机驱动所有行为(overlay.rs):

  • Hidden:窗口隐藏,只驻留托盘;
  • Drawing:覆盖层接管鼠标,可以画笔/荧光笔/箭头等 11 种工具标注;
  • Penetration:穿透模式,覆盖层保持可见但鼠标事件放穿给下层应用。

一次激活的完整流程在 activate_drawing() 中:先按当前鼠标所在显示器定位窗口 → 重新断言透明 →show()→ 设为置顶 → 通知前端 → 把工具栏抬到覆盖层之上。

焦点丢失时自动进入穿透模式

一个容易忽略的细节:Drawing 状态下用户点击了别的窗口,覆盖层会丢失焦点。MarkerOn 没有直接退出,而是延迟 120ms 确认后自动切入 Penetration 模式(on_overlay_focus_lost()),并配合 600ms 的"激活保护期"防止刚激活时误触发。这样标注画面保留,鼠标却已经可以操作下面的应用了。

Windows 实现:DWM 透明与"1 像素"技巧

双层透明:DWM 宿主窗口 + WebView2 背景

Windows 上的透明是"两层"的:宿主窗口靠 DWM 合成器,WebView2 内容靠自己的 Alpha 合成器。tao只在窗口创建时调用过一次DwmEnableBlurBehindWindow,而 DWM 在长时间空闲(尤其覆盖层带着WS_EX_LAYERED穿透样式时)可能悄悄丢掉这个状态,留下一片黑底。

MarkerOn 的对策是"每次激活都重新断言"(reapply_overlay_transparency()):

  • 重新调用DwmEnableBlurBehindWindow,传入一个空模糊区域,让宿主窗口完全透明;
  • 同时调用 Tauri 的set_background_color(0,0,0,0),重新设置 WebView2 的DefaultBackgroundColor。

此外,每次窗口重新获得焦点、以及 DPI 缩放变化时(lib.rs)都会再次执行这套断言,因为 WebView2 的 GPU 进程可能在这些时刻重建渲染器。

2 秒脉冲重断言:对抗 GPU 回收

DPI 变化或显示器移动可能触发 WebView2 完全重建合成器,而重建时间可能远超一次修复。notify_overlay_geometry_changed()(overlay.rs)会启动一条脉冲序列,在 50ms / 150ms / 500ms / 1000ms / 2000ms 各断言一次透明并重发几何变更事件,确保"黑底闪回"在任何时刻都能被兜住。

置顶与"1 像素"技巧

置顶通过SetWindowPos(HWND_TOPMOST)实现,但有一个精妙之处——position_window_on_monitor() 默认让窗口高度比显示器小 1 像素。因为 Windows 11 会把"恰好铺满一块屏幕的顶层窗口"当作全屏应用处理,从而剥离任务栏的 Mica 透明效果,导致任务栏闪烁。

反过来,如果你正在另一个全屏应用(浏览器 F11、视频)上启动标注,这个 1px 缝隙又会导致 Explorer 把任务栏抬到覆盖层之上。于是 MarkerOn 先用 fullscreen_foreground_window() 检测前台窗口是否铺满当前显示器(排除桌面、带标题栏窗口等),若命中则cover_monitor = true铺满整屏,并用 show_covering_window_without_activation() 以SW_SHOWNA显示后再连续两次SetWindowPos改尺寸——因为 Windows 只在可见窗口被调整大小时才重新计算"是否全屏",而隐藏状态下改尺寸会留下过期的判定,导致首次激活瞬间任务栏窜到全屏应用上面。

退出标注时,deactivate_drawing() 还会把前台焦点还给原本被覆盖的全屏应用(restore_foreground_window()),并只在全屏应用确实被覆盖层持有焦点时才切换,绝不偷走其他窗口的焦点。

光标禁锢与录屏兼容

  • 光标禁锢:进入 Drawing 后用ClipCursor把系统光标限制在当前标注的显示器内(remember_and_clip_drawing_monitor()),防止你画着画着鼠标跑到第二块屏幕;穿透模式临时解除。
  • 录屏兼容:覆盖层通过SetWindowDisplayAffinity(WDA_EXCLUDEFROMCAPTURE)把自己从BitBlt截图中排除(win32.rs);同时 video_compat.rs 通过窗口子类化,在样式被改写时始终保留WS_EX_TOOLWINDOW标志——Chromium 会把工具窗口排除在原生遮挡检测之外,浏览器视频才不会因覆盖层"遮挡"而暂停。

macOS 实现:子窗口堆叠与键窗口管理

工具栏不是"平级窗口",而是覆盖层的"孩子"

macOS 上最棘手的问题是堆叠顺序:工具栏和覆盖层都设为always_on_top时,点一下画布就可能把覆盖层抬到工具栏之上,点一下面板又可能反过来。ignoresMouseEvents这类平级窗口 z-order 方案不可靠。

MarkerOn 的解法是直接利用 AppKit 的父子窗口关系(order_toolbar_above_overlay_now()):调用addChildWindow:ordered:把工具栏挂成覆盖层的子窗口,排在NSWindowAbove。子窗口永远压在父窗口之上,画布点击怎么重排都不会把墨迹画到面板上。代码注释里还留了一条红线:只允许调用 NSWindow 的选择器,绝不能碰 WKWebView / WryWebView 的 ObjC 接口(会直接崩溃)。

点击穿透:用"键窗口"切换代替 ignoresMouseEvents

既然工具栏是子窗口,它天然赢得命中测试,不需要再对父覆盖层开ignoresMouseEvents(旧方案反而会让子窗口收不到 hover)。现在的做法很优雅(set_overlay_ignore_cursor_events()):

  • 指针进入面板 →activate_toolbar_for_pointer_interaction()让工具栏成为 key window 并开启 mouse-moved 事件,hover 高亮、按钮无需先"激活点击"就能用;
  • 指针离开面板 →activate_overlay_for_drawing()把 key 身份还给覆盖层,继续收画笔事件。

两个窗口都固定在NSFloatingWindowLevel(即 Taurialways_on_top对应的层级),真正的堆叠交给子窗口关系负责。

没有 ClipCursor:8ms 轮询 + 光标回弹

macOS 没有公开的ClipCursorAPI。MarkerOn 在 macos_cursor.rs 里起了一个独立线程,每 8ms 用CGEventCreate+CGEventGetLocation采样一次光标位置,一旦越出标注显示器的边界,就调用CGWarpMouseCursorPosition把光标"弹"回边界内。效果与 Windows 的ClipCursor一致,但实现是轮询式硬约束。

截屏排除与 Accessory 激活策略

  • 截屏排除:工具栏调用setSharingType: NSWindowSharingNone(macos.rs),与 Windows 的WDA_EXCLUDEFROMCAPTURE对应——面板不会出现在"复制白板"的截图里;
  • 无 Dock 图标:应用以ActivationPolicy::Accessory运行(纯托盘/菜单栏应用),只有打开设置窗口时才临时切到Regular,关闭后恢复(macos.rs)。

两端差异速查表

能力Windows (win32)macOS (AppKit)
窗口透明DwmEnableBlurBehindWindow空区域 + WebView2 背景色Wry 自动禁用白底 +set_background_color(0,0,0,0)
永远置顶SetWindowPos(HWND_TOPMOST)setLevel:NSFloatingWindowLevel
工具栏压住墨迹顶层组内重排 Z-orderaddChildWindow:ordered:子窗口
点击穿透WS_EX_TRANSPARENT子窗口天然胜命中测试 + key 窗口切换
光标禁锢ClipCursor系统 API8ms 轮询 +CGWarpMouseCursorPosition
截屏排除WDA_EXCLUDEFROMCAPTURENSWindowSharingNone
几何单位物理像素逻辑点(points)

写在最后

MarkerOn 的透明覆盖层没有任何"魔法",靠的是一系列被踩坑后沉淀下来的防御性细节:1 像素的缝隙、2 秒的脉冲重断言、只在焦点真正被覆盖层持有时才归还前台、子窗口替代 z-order 博弈。这些技巧对任何想做悬浮工具、录屏标注、白板应用的开发者都是直接可复用的。

想继续深入,建议从 src-tauri/src/overlay.rs 的状态机读起,再对照 src-tauri/src/win32.rs 和 src-tauri/src/macos.rs 看两端实现的分叉点;README.md 里有完整的快捷键清单与功能说明,README_zh.md 为中文版。

【免费下载链接】markeron

Lightweight (~1.5 MB) open-source screen annotation with click-through mode and keyboard-first shortcuts. For demos, teaching, meetings & screen recording. Windows, macOS.

项目地址:https://gitcode.com/gh_mirrors/mar/markeron
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 15:20:50

PL/SQL Developer远程连接排障核心:客户端、tnsnames与字符集三要素

1. 这不是“装个软件”那么简单:PL/SQL Developer 配置背后的真实战场你搜“PLSQL安装配置”,页面上跳出来的大多是“三步搞定”“5分钟上手”的教程,点进去一看,无非是下载、解压、填个tnsnames.ora路径——然后呢?然…

作者头像 李华
网站建设 2026/10/20 11:26:43

岭回归与LASSO实战:应对多重共线性与过拟合的正则化方法

接手一个数据分析需求时,我遇到过这么一档子事:数据文件里有四十多个特征列,样本量却只有两百出头。用普通线性回归跑完之后,训练集上R漂亮得能发朋友圈,一到测试集就现出原形——预测结果跟瞎猜差不多。更要命的是&am…

作者头像 李华
网站建设 2026/10/23 4:27:19

MySQL Day3分水岭:SQL执行八阶段深度解析

1. 项目概述:为什么“Day3”才是MySQL真正入门的分水岭很多人看到“MySQL一周从入门到精通”这个标题,第一反应是:又一个速成噱头。但做过十年数据库教学和一线运维的我,反而特别关注这个“Day3”。不是因为前三天最辛苦&#xff…

作者头像 李华
网站建设 2026/10/22 18:04:03

React Native迁回原生:12周精准迁移实战指南

1. 这不是一次简单的“回滚”,而是一场面向原生体验的精准外科手术Shopify把用了三年的React Native应用,在12周内完整迁回Swift和Kotlin——这个标题里藏着的,远不止技术栈切换这么简单。它背后是移动开发领域一个正在加速成型的共识&#x…

作者头像 李华
网站建设 2026/10/18 21:00:18

第一性原理计算应力-应变曲线的完整工作流与弹性常数拟合

简介:本资源是一套面向材料计算初学者与科研实践者的Python自动化分析工具包,聚焦于VASP与Quantum Espresso(QE)第一性原理模拟后的应力-应变关系建模与力学参数提取。针对DFT计算中反复修改晶格、批量读取输出、拟合弹性常数等繁…

作者头像 李华