AIGC标识 摸鱼笔记[8]-OnTopReplica窗口克隆工具

摘要

在windows10上使用OnTopReplica将窗口画面复制到多个显示器上.

声明

本文内容由 AI 辅助生成, 已经人工审核和编辑。

简介

OnTopReplica简介

[https://github.com/LorenzCK/OnTopReplica]
OnTopReplica 是一款适用于 Windows Vista/7/8/10 的实时置顶窗口克隆工具。

启动后默认显示一个空白置顶窗口,用户可选择系统中任意其他窗口进行实时克隆,并保持始终置顶。非常适合监控后台进程、操作复杂多窗口游戏或工具、边工作边观看 YouTube 视频等场景。

📢 主要功能:

  • 克隆任意窗口并保持始终置顶,不影响其他窗口操作
  • 支持选择克隆窗口的子区域,可保存常用区域,或使用相对于目标窗口边框的相对坐标
  • 自动缩放(适配原始窗口、1/2、1/4 及全屏模式)
  • 屏幕角落位置锁定
  • 可调节透明度
  • 点击转发:允许与克隆窗口进行交互
  • 穿透点击:忽略鼠标交互(配合部分透明度可将 OnTopReplica 作为悬浮覆盖层使用)
  • 分组切换模式:自动在常用窗口组间切换

系统要求:

  • Windows Vista 或更高版本(使用原生 DWM 缩略图技术实现克隆)
  • Microsoft .NET Framework 4.7
  • 开启桌面合成(即 Windows Aero 效果)

安装方式:

Releases 页面下载 MSI 安装包。

开源协议:

采用 MS-RL (Microsoft Reciprocal License) 许可证。欢迎提交 Issue 和反馈,也欢迎 Fork 贡献代码!

Windows DWM (Desktop Window Manager)简介

[https://learn.microsoft.com/en-us/windows/win32/dwm/dwm-overview]
Desktop Window Manager(DWM,桌面窗口管理器) 是微软 Windows 操作系统中的一个核心图形合成组件,最初随 Windows Vista 引入,作为桌面合成(Desktop Composition)功能的核心执行者。它从根本上改变了应用程序在屏幕上显示像素的方式。

核心原理:桌面合成

在 DWM 出现之前(Windows XP 及更早),应用程序窗口直接绘制到屏幕显示缓冲区。DWM 引入的桌面合成技术将这一过程改为:

  1. 离屏渲染:每个应用程序窗口不再直接绘制到屏幕,而是将其绘图内容重定向到视频内存中的离屏表面(Off-screen Surface)
  2. 统一合成:DWM 收集所有窗口的离屏表面,利用 GPU 硬件加速将它们合成为最终的桌面图像
  3. 输出显示:合成后的完整桌面图像输出到显示器

这种架构使得 DWM 能够实现传统渲染方式无法做到的高级视觉效果,同时避免了窗口重叠时的重绘问题和画面撕裂。

技术架构

DWM 基于 DirectX(特别是 Direct3D)Windows 显示驱动程序模型(WDDM) 构建,本质上是一个全屏的 Direct3D 应用程序。它利用 GPU 进行硬件加速合成,将每个窗口创建为 Direct3D 纹理,映射到 3D 空间中的网格上进行合成。窗口的打开、关闭、最小化等过渡效果,实际上是这些 3D 矩形的变换动画。

主要功能与特性

功能 说明
Aero Glass 玻璃窗口效果,支持半透明和模糊背景
实时任务栏缩略图 鼠标悬停任务栏时显示窗口实时预览
Flip / Flip 3D Alt+Tab 窗口切换时的 3D 动画效果
窗口过渡动画 最小化、最大化、关闭的平滑动画
高 DPI 缩放 支持高分辨率显示器的 DPI 感知缩放
实时缩略图 API 允许第三方应用在其他窗口中显示实时窗口缩略图
双缓冲机制 使用翻转(Flipping)技术避免画面撕裂

DWM 缩略图 API

DWM 提供了一组专门的 API 用于在应用程序中显示其他窗口的实时缩略图:

API 函数 功能
DwmRegisterThumbnail 在目标窗口和源窗口之间创建缩略图关系
DwmUpdateThumbnailProperties 更新缩略图属性(位置、大小、透明度、裁剪区域等)
DwmQueryThumbnailSourceSize 获取源窗口的原始尺寸
DwmUnregisterThumbnail 注销缩略图关系

这些 API 的关键特点是:缩略图不是静态截图,而是动态、持续的连接。源窗口的更新会实时反映在缩略图中,这正是 OnTopReplica 实现窗口克隆的核心技术基础。

系统要求与演进

  • Windows Vista:DWM 首次引入,作为可选功能(Aero 主题),可手动关闭
  • Windows 7:GDI 渲染获得硬件加速(配合 WDDM 1.1 驱动)
  • Windows 8 及以后:DWM 成为始终开启的核心系统组件,无法禁用
  • Windows 10/11:DWM 深度集成虚拟桌面、动态刷新率(DRR)、多平面叠加(MPO)等现代显示技术

为什么 OnTopReplica 依赖 DWM

OnTopReplica 的核心功能——窗口实时克隆——完全建立在 DWM 缩略图 API 之上。没有 DWM(如 Windows XP 或 DWM 被禁用的情况),程序无法获取其他窗口的实时渲染内容,因此 DWM 是 OnTopReplica 运行的必要条件。这种设计的好处是:

  • 性能优异:缩略图由 DWM 在 GPU 层面合成,几乎零 CPU 开销
  • 实时更新:源窗口的任何变化即时反映,无需轮询或截屏
  • 系统级支持:利用原生 API,稳定可靠,兼容性好

工程

核心原理

OnTopReplica 的核心技术架构围绕 Windows DWM (Desktop Window Manager) 展开,利用系统级 API 实现高级窗口管理功能。以下是其五大核心原理:

1. 窗口克隆 — DWM 实时缩略图

OnTopReplica 并非截屏或录制视频,而是调用 Windows DWM API 在自身窗口中注册目标窗口的实时缩略图。这是 Vista+ 系统提供的原生功能,目标窗口的更新会实时反映在克隆窗口中,性能开销极低。

使用的核心 API:

API 类别 具体 API 用途
DWM DwmRegisterThumbnail 注册窗口缩略图
DWM DwmUpdateThumbnailProperties 更新缩略图属性(位置、大小、透明度、裁剪区域)
DWM DwmUnregisterThumbnail 注销缩略图

依赖的第三方库 WindowsFormsAero 提供了这些 API 的 .NET 封装(DwmManagerThumbnail 类)。

2. 始终置顶 — TopMost + DWM 排除

  • 基础:WinForms 的 TopMost = true 属性
  • 防失焦修复:在 OnDeactivate 事件中重新设置 TopMost
  • DWM 排除(Windows 7+):通过 SetWindowFlip3dPolicySetExcludeFromPeekSetDisallowPeek 确保窗口在 Alt+TabAero Peek 时不会被覆盖

3. 点击转发 — 坐标映射 + PostMessage 注入

  1. 在克隆窗口上捕获鼠标点击事件
  2. 将点击坐标按比例映射到源窗口的像素坐标(补偿内边距和裁剪区域偏移)
  3. 通过 RealChildWindowFromPoint 递归查找实际接收点击的子控件
  4. 使用 PostMessage 向目标控件注入 WM_LBUTTONDOWN / WM_LBUTTONUP 等鼠标消息

4. 窗口监听 — Shell Hook

通过 RegisterShellHookWindow 注册 Shell Hook,监听系统窗口事件(创建、销毁、激活),实现:

  • 当克隆窗口被关闭时自动取消克隆(WindowKeeper)
  • 全局快捷键响应(HotKeyManager)
  • 组切换模式自动切换窗口(GroupSwitchManager)

5. 平台适配

根据 Environment.OSVersion 检测 Windows 版本,不同版本提供不同的行为。DWM 是必需条件——没有 DWM(Windows XP 或 DWM 被禁用),程序无法运行。

核心代码

注册 DWM 缩略图(ThumbnailPanel.cs)

public void SetThumbnailHandle(WindowHandle handle, ThumbnailRegion region) {
    // 关闭旧的缩略图
    if (_thumbnail != null && !_thumbnail.IsInvalid) {
        _thumbnail.Close();
        _thumbnail = null;
    }

    Form owner = this.TopLevelControl as Form;
    
    // 注册新的 DWM 缩略图!
    _thumbnail = DwmManager.Register(owner, handle.Handle);
    _currentRegion = region;
    _regionEnabled = (region != null);
    UpdateThubmnail();
}

更新缩略图属性(ThumbnailPanel.cs)

private void UpdateThubmnail() {
    if (_thumbnail != null && !_thumbnail.IsInvalid){
        try {
            Size sourceSize = ThumbnailPixelSize;
            _thumbnailSize = sourceSize.Fit(Size);  // 保持宽高比适配
            _padWidth = (Size.Width - _thumbnailSize.Width) / 2;
            _padHeight = (Size.Height - _thumbnailSize.Height) / 2;

            var target = new Rectangle(_padWidth, _padHeight, _thumbnailSize.Width, _thumbnailSize.Height);
            
            Rectangle source = (_regionEnabled) 
                ? _currentRegion.ComputeRegionRectangle(_thumbnail.GetSourceSize()) 
                : new Rectangle(Point.Empty, _thumbnail.GetSourceSize());

            // 更新 DWM 缩略图属性
            _thumbnail.Update(target, source, ThumbnailOpacity, true, true);
        }
        catch {
            UnsetThumbnail();
            return;
        }
    }
}

坐标转换(ThumbnailPanel.cs)

protected Point ClientToThumbnail(Point position) {
    position.X -= _padWidth;
    position.Y -= _padHeight;
    
    PointF proportionalPosition = new PointF(
        (float)position.X / _thumbnailSize.Width,
        (float)position.Y / _thumbnailSize.Height
    );
    
    Size source = ThumbnailPixelSize;
    Point offset = (_regionEnabled) ? SelectedRegion.Offset : Point.Empty;
    
    return new Point(
        (int)((proportionalPosition.X * source.Width) + offset.X),
        (int)((proportionalPosition.Y * source.Height) + offset.Y)
    );
}

注入鼠标点击(Win32Helper.cs)

public static void InjectFakeMouseClick(IntPtr window, CloneClickEventArgs clickArgs) {
    NPoint clientClickLocation = NPoint.FromPoint(clickArgs.ClientClickLocation);
    NPoint scrClickLocation = WindowManagerMethods.ClientToScreen(window, clientClickLocation);
    
    IntPtr hMenu = WindowMethods.GetMenu(window);
    if (hMenu != IntPtr.Zero)
        scrClickLocation.Y -= SystemInformation.MenuHeight;
    
    IntPtr hChild = GetRealChildControlFromPoint(window, scrClickLocation);
    NPoint clntClickLocation = WindowManagerMethods.ScreenToClient(hChild, scrClickLocation);
    
    if (clickArgs.Buttons == MouseButtons.Left) {
        if(clickArgs.IsDoubleClick)
            InjectDoubleLeftMouseClick(hChild, clntClickLocation);
        else
            InjectLeftMouseClick(hChild, clntClickLocation);
    }
}

private static void InjectLeftMouseClick(IntPtr child, NPoint clientLocation) {
    IntPtr lParamClickLocation = MessagingMethods.MakeLParam(clientLocation.X, clientLocation.Y);
    MessagingMethods.PostMessage(child, WM.LBUTTONDOWN, new IntPtr(MK.LBUTTON), lParamClickLocation);
    MessagingMethods.PostMessage(child, WM.LBUTTONUP, new IntPtr(MK.LBUTTON), lParamClickLocation);
}

消息泵处理器(MessagePumpManager.cs)

public void Initialize(MainForm form) {
    Form = form;
    HookMethods.RegisterShellHookWindow(form.Handle);
    
    Register(new WindowKeeper(), form);       // 监听窗口销毁
    Register(new HotKeyManager(), form);    // 全局快捷键
    Register(new GroupSwitchManager(), form); // 组切换模式
    Register(new FlashCloner(), form);      // 自动克隆闪烁窗口
}

public bool PumpMessage(ref Message msg) {
    foreach (var processor in _processors.Values) {
        if (processor.Process(ref msg))
            return true;
    }
    return false;
}
posted @ 2026-07-13 21:21  qsBye  阅读(2)  评论(0)    收藏  举报