添加文档

This commit is contained in:
Xu Liu 2021-03-15 21:39:07 +08:00
commit 2a1df02e1d
5 changed files with 50 additions and 19 deletions

View file

@ -33,9 +33,9 @@ namespace Magpie.CursorHook {
private readonly IntPtr _hwndSrc;
private readonly (int x, int y) _cursorSize = NativeMethods.GetCursorSize();
// 用于保存窗口类的 HCURSOR,以在卸载钩子时还原
private readonly Dictionary<IntPtr, IntPtr> _hwndTohCursor = new Dictionary<IntPtr, IntPtr>();
// 保存已替换 HCURSOR 的窗口,以在卸载钩子时还原
private readonly HashSet<IntPtr> _replacedHwnds = new HashSet<IntPtr>();
// 原光标到透明光标的映射
private readonly Dictionary<IntPtr, IntPtr> _hCursorToTptCursor = new Dictionary<IntPtr, IntPtr>();
@ -94,7 +94,7 @@ namespace Magpie.CursorHook {
// 将窗口类中的 HCURSOR 替换为透明光标
void replaceHCursor(IntPtr hWnd) {
if (_hwndTohCursor.ContainsKey(hWnd)) {
if (_replacedHwnds.Contains(hWnd)) {
return;
}
@ -110,7 +110,7 @@ namespace Magpie.CursorHook {
// 替换窗口类的 HCURSOR
if (NativeMethods.SetClassLong(hWnd, NativeMethods.GCLP_HCURSOR, hTptCursor) == hCursor) {
_hwndTohCursor[hWnd] = hCursor;
_replacedHwnds.Add(hWnd);
} else {
ReportIfFalse(false, "SetClassLong 失败");
}
@ -127,7 +127,7 @@ namespace Magpie.CursorHook {
// 替换窗口类的 HCURSOR
if (NativeMethods.SetClassLong(hWnd, NativeMethods.GCLP_HCURSOR, hTptCursor) == hCursor) {
// 替换成功
_hwndTohCursor[hWnd] = hCursor;
_replacedHwnds.Add(hWnd);
_hCursorToTptCursor[hCursor] = hTptCursor;
} else {
NativeMethods.DestroyCursor(hTptCursor);
@ -173,7 +173,7 @@ namespace Magpie.CursorHook {
),
"PostMessage 失败"
);
try {
// Loop until FileMonitor closes (i.e. IPC fails)
while (true) {
@ -201,9 +201,23 @@ namespace Magpie.CursorHook {
}
// 退出前重置窗口类的光标
foreach (var item in _hwndTohCursor) {
foreach (var hwnd in _replacedHwnds) {
IntPtr hCursor = (IntPtr)NativeMethods.GetClassLong(hwnd, NativeMethods.GCLP_HCURSOR);
if (hCursor == IntPtr.Zero) {
continue;
}
// 在 _hCursorToTptCursor 中反向查找
var item = _hCursorToTptCursor
.FirstOrDefault(pair => pair.Value == hCursor);
if(item.Key == IntPtr.Zero || item.Value == IntPtr.Zero) {
// 找不到就不替换
continue;
}
// 忽略错误
NativeMethods.SetClassLong(item.Key, NativeMethods.GCLP_HCURSOR, item.Value);
NativeMethods.SetClassLong(hwnd, NativeMethods.GCLP_HCURSOR, item.Key);
}
// 清理资源
@ -223,10 +237,9 @@ namespace Magpie.CursorHook {
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode, SetLastError = true)]
public delegate IntPtr SetCursor_Delegate(IntPtr hCursor);
// 取代 SetCursor 的钩子
public IntPtr SetCursor_Hook(IntPtr hCursor) {
ReportToServer("setcursor");
// ReportToServer("setcursor");
if (!NativeMethods.IsWindow(_hwndHost) || hCursor == IntPtr.Zero) {
// 全屏窗口关闭后钩子不做任何操作
@ -309,7 +322,9 @@ namespace Magpie.CursorHook {
private void ReportToServer(string msg) {
#if DEBUG
_messageQueue.Enqueue(msg);
if(_messageQueue.Count < 1000) {
_messageQueue.Enqueue(msg);
}
#endif
}

View file

@ -2,21 +2,19 @@
窗口放大镜!
可以将任意窗口全屏显示,支持高级缩放算法,包括 Jinc2、[Anime4K](https://github.com/bloc97/Anime4K)本项目包含一个hlsl移植、Lanczos6等。
可以将任意窗口全屏显示,支持高级缩放算法,包括 Jinc、[Anime4K](https://github.com/bloc97/Anime4K)本项目包含一个hlsl移植、Lanczos等。
主要用于游戏窗口的放大显示,适用于那些不支持全屏模式,或者游戏自带的全屏模式会使画面模糊的情况。
本项目还处于早期阶段欢迎fork和star欢迎任何形式的贡献。
注意欲使用本程序你需要一个性能足够的GPU尤其是使用Anime4K缩放模式时。
## 窗口截图
![窗口截图](img/窗口截图.png)
使用方法:程序启动后,激活要放大的窗口,按下热键即可全屏显示该窗口,再次按下热键将退出全屏。
目前缩放模式仅支持通用Jinc2+自适应锐化以及Anime4KAnime4K+HQBicubic程序内使用json因此你可以轻松地组合出自己的缩放模式
目前缩放模式仅支持通用Jinc2+自适应锐化以及Anime4KAnime4K+mitchell+自适应锐化程序内使用json因此你可以轻松地组合出自己的缩放模式
## 效果截图
@ -42,9 +40,13 @@
![Anime4K_放大后](img/Anime4K_放大后.png)
## 已知限制
## 实现原理
由于实现的限制,在帧数较低时鼠标将运动迟缓。目前不支持使用自定义鼠标的窗口。
尽管功能与[Lossless Scaling](https://store.steampowered.com/app/993090/Lossless_Scaling/)和[IntegerScaler](https://tanalin.com/en/projects/integer-scaler/)类似但本程序的实现原理与它们完全不同。Lossless Scaling和IntegerScaler使用[Magnification API](https://docs.microsoft.com/en-us/previous-versions/windows/desktop/magapi/entry-magapi-sdk)实现对窗口的放大但此API无法实现高级缩放算法其核心函数[MagSetImageScalingCallback](https://docs.microsoft.com/en-us/windows/win32/api/magnification/nf-magnification-magsetimagescalingcallback)已被废弃因此它们必须与显卡驱动打交道而你的显卡很可能不被支持。此外它们只支持整数倍的放大这极大限制了它们的使用场景。举例来说它们无法把一个1024x768大小的窗口放大到1920x1080。
本程序使用了一个十分符合直觉的方式放大窗口使用一个全屏窗口覆盖屏幕捕获原窗口的内容放大后在该全屏窗口显示出来。这种方式使得缩放算法不受任何限制让我们可以自由使用现存的优秀缩放算法。为了使用GPU加速全屏窗口使用了Direct2D技术将缩放算法实现为[Direct2D Effect](https://docs.microsoft.com/en-us/windows/win32/direct2d/effects-overview)通过Effect的堆叠我们可以用任何方式缩放窗口以取得完美的效果。
这种方案唯一的限制便是系统光标因此这里使用了一点hack将系统的光标替换为透明然后在全屏窗口上绘制它因此虽然光标始终处于源窗口内但其不可见。大多数情况下这些更改不会被用户感知到尽管如此如果源窗口使用了自定义光标用户会在屏幕上看到两个光标。为了解决这个问题我们提供了一个更深入的hack选项即注入源窗口的进程将其自定义光标也替换为透明然后在全屏窗口上将其绘制更深入的解释见[光标映射](./光标映射.md)。大多数情况下它可以工作的很好但因为Windows生态的复杂性实际效果还有待测试。
## 开发计划

View file

@ -26,7 +26,7 @@ public:
noDisturb
));
}
// 不可复制,不可移动
MagWindow(const MagWindow&) = delete;
MagWindow(MagWindow&&) = delete;

BIN
img/CursorHook.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

14
光标映射.md Normal file
View file

@ -0,0 +1,14 @@
# Magpie中的光标映射
CursorHook的功能是隐藏源窗口中的光标并在全屏窗口上绘制该光标。下图演示了它的工作原理
![CursorHook工作原理](img/CursorHook.png)
1. Magpie使用[EasyHook](http://easyhook.github.io/)将CursorHook.dll注入源窗口进程替换源窗口中的[SetCursor](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setcursor)
2. 源窗口试图使用SetCursor设置自定义光标
3. 注入程序维护一个源光标到对应透明光标的映射首先在该映射中查找该光标。如果找到使用该光标对应的透明光标调用系统的SetCursor转到第7步如果未找到转到第4步
4. 创建一个热点相同的透明光标,创建新的映射
5. 使用PostMessage将该映射通知全屏窗口。全屏窗口维护着一个透明光标到源光标的映射收到消息后创建该映射
6. 使用第4步创建的透明光标调用系统的SetCursor
7. 全屏窗口渲染时使用[GetCursorInfo](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getcursorinfo)获取当前光标,此时系统返回注入程序里设置的透明光标
8. 全屏窗口在映射中查找该透明光标,并在窗口上绘制其对应的源光标