Skip to content

Commit d068b13

Browse files
authored
feat: Win32 截图方式测速 (#1183)
1 parent f834f44 commit d068b13

10 files changed

Lines changed: 212 additions & 81 deletions

File tree

docs/en_us/2.4-ControlMethods.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ Different programs on Win32 handle input differently, so there is no universal m
8080

8181
> Reference: [MaaDef.h](https://github.com/MaaXYZ/MaaFramework/blob/main/include/MaaFramework/MaaDef.h#L242-L250)
8282
83-
Select one of the values below.
83+
Combine the selected methods below using **bitwise OR** to provide a single value. MaaFramework will try all provided methods and select the fastest available method.
8484

85-
No default value. Client can choose one as the default.
85+
No default value. Client can choose one combination as the default.
8686

8787
Different programs on Win32 handle rendering differently, so there is no universal method.
8888

@@ -97,6 +97,12 @@ Different programs on Win32 handle rendering differently, so there is no univers
9797

9898
> [!NOTE]
9999
>
100+
> Three predefined combination macros are provided:
101+
>
102+
> - `MaaWin32ScreencapMethod_All`: all screencap methods
103+
> - `MaaWin32ScreencapMethod_Foreground`: `DXGI_DesktopDup_Window | ScreenDC`
104+
> - `MaaWin32ScreencapMethod_Background`: `FramePool | PrintWindow`
105+
>
100106
> FramePool and PrintWindow have built-in pseudo-minimize support: when the target window is minimized, they make it transparent and click-through, then restore it without activation, allowing screencap to continue without disturbing the user.
101107
> Other screencap methods will fail when the target window is minimized. Please avoid minimizing the window when using those methods.
102108
@@ -178,4 +184,4 @@ The wlroots compositor to be controlled must support the following protocols:
178184
* Wayland Core Protocols
179185
* `virtual-keyboard-unstable-v1`
180186
* `wlr-screencopy-unstable-v1`
181-
* `wlr-virtual-pointer-unstable-v1`
187+
* `wlr-virtual-pointer-unstable-v1`

docs/zh_cn/2.4-控制方式说明.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,9 @@ Win32 下不同程序处理输入的方法不同,不存在一个通用方式
8080

8181
> 参考 [MaaDef.h](https://github.com/MaaXYZ/MaaFramework/blob/main/include/MaaFramework/MaaDef.h#L242-L250)
8282
83-
选择下面的值提供
83+
将下面选择的方式 **按位或** 合并为一个值提供。MaaFramework 将会尝试所有提供的方式,选择最快的可用方式
8484

85-
无默认值。Client 可以选择一个作为默认值
85+
无默认值。Client 可以选择一个组合作为默认值
8686

8787
Win32 下不同程序处理绘制的方法不同,不存在一个通用方式。
8888

@@ -97,6 +97,12 @@ Win32 下不同程序处理绘制的方法不同,不存在一个通用方式
9797

9898
> [!NOTE]
9999
>
100+
> 提供了三个组合宏便于直接使用:
101+
>
102+
> - `MaaWin32ScreencapMethod_All`:所有截图方式
103+
> - `MaaWin32ScreencapMethod_Foreground``DXGI_DesktopDup_Window | ScreenDC`
104+
> - `MaaWin32ScreencapMethod_Background``FramePool | PrintWindow`
105+
>
100106
> FramePool 和 PrintWindow 内置了伪最小化支持:当目标窗口被最小化时,会将窗口设为透明并开启点击穿透,以不激活的方式恢复窗口,从而在不打扰用户的情况下继续截图。
101107
> 其他截图方式在窗口最小化后无法获取有效内容,请避免窗口最小化。
102108

include/MaaFramework/MaaDef.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,12 +294,17 @@ typedef uint64_t MaaAdbInputMethod;
294294

295295
// MaaWin32ScreencapMethod:
296296
/**
297-
* @brief Win32 screencap method
297+
* @brief Win32 screencap method flags
298298
*
299-
* No bitwise OR, select ONE method only.
299+
* Use bitwise OR to set the methods you need.
300+
* MaaFramework will test all provided methods and use the fastest available one.
300301
*
301302
* No default value. Client should choose one as default.
302303
*
304+
* Predefined combinations:
305+
* - Foreground: DXGI_DesktopDup_Window | ScreenDC
306+
* - Background: FramePool | PrintWindow
307+
*
303308
* Different applications use different rendering methods, there is no universal solution.
304309
*
305310
* | Method | Speed | Compatibility | Require Admin | Background Support | Notes |
@@ -325,6 +330,11 @@ typedef uint64_t MaaWin32ScreencapMethod;
325330
#define MaaWin32ScreencapMethod_PrintWindow (1ULL << 4)
326331
#define MaaWin32ScreencapMethod_ScreenDC (1ULL << 5)
327332

333+
#define MaaWin32ScreencapMethod_All (~MaaWin32ScreencapMethod_None)
334+
#define MaaWin32ScreencapMethod_Foreground \
335+
(MaaWin32ScreencapMethod_DXGI_DesktopDup_Window | MaaWin32ScreencapMethod_ScreenDC)
336+
#define MaaWin32ScreencapMethod_Background (MaaWin32ScreencapMethod_FramePool | MaaWin32ScreencapMethod_PrintWindow)
337+
328338
// MaaWin32InputMethod:
329339
/**
330340
* @brief Win32 input method

source/MaaWin32ControlUnit/Manager/Win32ControlUnitMgr.cpp

Lines changed: 133 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#include "Win32ControlUnitMgr.h"
22

3+
#include <chrono>
4+
35
#include "MaaFramework/MaaMsg.h"
46
#include "MaaUtils/Logger.h"
7+
#include "MaaUtils/Time.hpp"
58

69
#include "Input/LegacyEventInput.h"
710
#include "Input/MessageInput.h"
@@ -34,6 +37,7 @@ Win32ControlUnitMgr::Win32ControlUnitMgr(
3437
bool Win32ControlUnitMgr::connect()
3538
{
3639
connected_ = false;
40+
screencap_.reset();
3741

3842
#ifndef MAA_WIN32_COMPATIBLE
3943
// 设置 Per-Monitor DPI Aware V2,确保 GetClientRect/GetWindowRect 等 API 返回物理像素。
@@ -52,77 +56,14 @@ bool Win32ControlUnitMgr::connect()
5256
LogError << "hwnd_ is invalid";
5357
return false;
5458
}
55-
56-
// FramePool 和 PrintWindow 内置伪最小化支持,允许最小化窗口
57-
bool supports_minimized =
58-
screencap_method_ == MaaWin32ScreencapMethod_FramePool || screencap_method_ == MaaWin32ScreencapMethod_PrintWindow;
59-
if (!supports_minimized && IsIconic(hwnd_)) {
60-
LogError << "hwnd_ is minimized";
61-
return false;
62-
}
6359
}
6460
else {
6561
LogWarn << "hwnd_ is nullptr";
6662
}
6763

68-
switch (screencap_method_) {
69-
case MaaWin32ScreencapMethod_GDI:
70-
screencap_ = std::make_shared<GdiScreencap>(hwnd_);
71-
break;
72-
case MaaWin32ScreencapMethod_FramePool:
73-
screencap_ = std::make_shared<FramePoolWithPseudoMinimizeScreencap>(hwnd_);
74-
break;
75-
case MaaWin32ScreencapMethod_DXGI_DesktopDup:
76-
screencap_ = std::make_shared<DesktopDupScreencap>(hwnd_);
77-
break;
78-
case MaaWin32ScreencapMethod_DXGI_DesktopDup_Window:
79-
screencap_ = std::make_shared<DesktopDupWindowScreencap>(hwnd_);
80-
break;
81-
case MaaWin32ScreencapMethod_PrintWindow:
82-
screencap_ = std::make_shared<PrintWindowWithPseudoMinimizeScreencap>(hwnd_);
83-
break;
84-
case MaaWin32ScreencapMethod_ScreenDC:
85-
screencap_ = std::make_shared<ScreenDCScreencap>(hwnd_);
86-
break;
87-
88-
default:
89-
LogError << "Unknown screencap method: " << static_cast<int>(screencap_method_);
90-
break;
91-
}
92-
93-
auto make_input = [&](MaaWin32InputMethod method) -> std::shared_ptr<InputBase> {
94-
switch (method) {
95-
case MaaWin32InputMethod_Seize:
96-
return std::make_shared<SeizeInput>(hwnd_, false);
97-
case MaaWin32InputMethod_SendMessage:
98-
return std::make_shared<MessageInput>(hwnd_, MessageInput::Config { .mode = MessageInput::Mode::SendMessage });
99-
case MaaWin32InputMethod_PostMessage:
100-
return std::make_shared<MessageInput>(hwnd_, MessageInput::Config { .mode = MessageInput::Mode::PostMessage });
101-
case MaaWin32InputMethod_LegacyEvent:
102-
return std::make_shared<LegacyEventInput>(hwnd_, true);
103-
case MaaWin32InputMethod_PostThreadMessage:
104-
return std::make_shared<PostThreadMessageInput>(hwnd_);
105-
case MaaWin32InputMethod_SendMessageWithCursorPos:
106-
return std::make_shared<MessageInput>(
107-
hwnd_,
108-
MessageInput::Config { .mode = MessageInput::Mode::SendMessage, .with_cursor_pos = true, .block_input = false });
109-
case MaaWin32InputMethod_PostMessageWithCursorPos:
110-
return std::make_shared<MessageInput>(
111-
hwnd_,
112-
MessageInput::Config { .mode = MessageInput::Mode::PostMessage, .with_cursor_pos = true, .block_input = false });
113-
case MaaWin32InputMethod_SendMessageWithWindowPos:
114-
return std::make_shared<MessageInput>(
115-
hwnd_,
116-
MessageInput::Config { .mode = MessageInput::Mode::SendMessage, .with_window_pos = true, .block_input = false });
117-
case MaaWin32InputMethod_PostMessageWithWindowPos:
118-
return std::make_shared<MessageInput>(
119-
hwnd_,
120-
MessageInput::Config { .mode = MessageInput::Mode::PostMessage, .with_window_pos = true, .block_input = false });
121-
default:
122-
LogError << "Unknown input method: " << static_cast<int>(method);
123-
return nullptr;
124-
}
125-
};
64+
if (!init_screencap()) {
65+
return false;
66+
}
12667

12768
if (mouse_method_ == keyboard_method_) {
12869
mouse_ = make_input(mouse_method_);
@@ -137,6 +78,132 @@ bool Win32ControlUnitMgr::connect()
13778
return true;
13879
}
13980

81+
std::unordered_map<Win32ControlUnitMgr::ScreencapMethod, std::shared_ptr<ScreencapBase>> Win32ControlUnitMgr::build_screencap_units() const
82+
{
83+
std::unordered_map<ScreencapMethod, std::shared_ptr<ScreencapBase>> units;
84+
85+
const auto add = [&](MaaWin32ScreencapMethod flag, ScreencapMethod method, auto factory) {
86+
if (screencap_method_ & flag) {
87+
units.emplace(method, factory());
88+
}
89+
};
90+
91+
add(MaaWin32ScreencapMethod_GDI, ScreencapMethod::GDI, [this] { return std::make_shared<GdiScreencap>(hwnd_); });
92+
add(MaaWin32ScreencapMethod_FramePool, ScreencapMethod::FramePool, [this] {
93+
return std::make_shared<FramePoolWithPseudoMinimizeScreencap>(hwnd_);
94+
});
95+
add(MaaWin32ScreencapMethod_DXGI_DesktopDup, ScreencapMethod::DXGI_DesktopDup, [this] {
96+
return std::make_shared<DesktopDupScreencap>(hwnd_);
97+
});
98+
add(MaaWin32ScreencapMethod_DXGI_DesktopDup_Window, ScreencapMethod::DXGI_DesktopDup_Window, [this] {
99+
return std::make_shared<DesktopDupWindowScreencap>(hwnd_);
100+
});
101+
add(MaaWin32ScreencapMethod_PrintWindow, ScreencapMethod::PrintWindow, [this] {
102+
return std::make_shared<PrintWindowWithPseudoMinimizeScreencap>(hwnd_);
103+
});
104+
add(MaaWin32ScreencapMethod_ScreenDC, ScreencapMethod::ScreenDC, [this] { return std::make_shared<ScreenDCScreencap>(hwnd_); });
105+
106+
return units;
107+
}
108+
109+
bool Win32ControlUnitMgr::init_screencap()
110+
{
111+
if (screencap_method_ == MaaWin32ScreencapMethod_None) {
112+
LogWarn << "No screencap method selected";
113+
return true;
114+
}
115+
116+
auto units = build_screencap_units();
117+
if (units.empty()) {
118+
LogError << "No available screencap method to test";
119+
return false;
120+
}
121+
122+
screencap_ = speed_test(units);
123+
if (!screencap_) {
124+
LogError << "failed to select screencap method";
125+
return false;
126+
}
127+
128+
return true;
129+
}
130+
131+
std::shared_ptr<InputBase> Win32ControlUnitMgr::make_input(MaaWin32InputMethod method) const
132+
{
133+
switch (method) {
134+
case MaaWin32InputMethod_Seize:
135+
return std::make_shared<SeizeInput>(hwnd_, false);
136+
case MaaWin32InputMethod_SendMessage:
137+
return std::make_shared<MessageInput>(hwnd_, MessageInput::Config { .mode = MessageInput::Mode::SendMessage });
138+
case MaaWin32InputMethod_PostMessage:
139+
return std::make_shared<MessageInput>(hwnd_, MessageInput::Config { .mode = MessageInput::Mode::PostMessage });
140+
case MaaWin32InputMethod_LegacyEvent:
141+
return std::make_shared<LegacyEventInput>(hwnd_, true);
142+
case MaaWin32InputMethod_PostThreadMessage:
143+
return std::make_shared<PostThreadMessageInput>(hwnd_);
144+
case MaaWin32InputMethod_SendMessageWithCursorPos:
145+
return std::make_shared<MessageInput>(
146+
hwnd_,
147+
MessageInput::Config { .mode = MessageInput::Mode::SendMessage, .with_cursor_pos = true, .block_input = false });
148+
case MaaWin32InputMethod_PostMessageWithCursorPos:
149+
return std::make_shared<MessageInput>(
150+
hwnd_,
151+
MessageInput::Config { .mode = MessageInput::Mode::PostMessage, .with_cursor_pos = true, .block_input = false });
152+
case MaaWin32InputMethod_SendMessageWithWindowPos:
153+
return std::make_shared<MessageInput>(
154+
hwnd_,
155+
MessageInput::Config { .mode = MessageInput::Mode::SendMessage, .with_window_pos = true, .block_input = false });
156+
case MaaWin32InputMethod_PostMessageWithWindowPos:
157+
return std::make_shared<MessageInput>(
158+
hwnd_,
159+
MessageInput::Config { .mode = MessageInput::Mode::PostMessage, .with_window_pos = true, .block_input = false });
160+
default:
161+
LogError << "Unknown input method: " << static_cast<int>(method);
162+
return nullptr;
163+
}
164+
}
165+
166+
std::shared_ptr<ScreencapBase>
167+
Win32ControlUnitMgr::speed_test(const std::unordered_map<ScreencapMethod, std::shared_ptr<ScreencapBase>>& units) const
168+
{
169+
LogFunc;
170+
171+
ScreencapMethod fastest = ScreencapMethod::UnknownYet;
172+
auto cost = std::chrono::milliseconds::max();
173+
174+
auto check = [&fastest, &cost](ScreencapMethod method, std::chrono::steady_clock::time_point start) {
175+
auto duration = duration_since(start);
176+
if (duration < cost) {
177+
fastest = method;
178+
cost = duration;
179+
}
180+
LogInfo << VAR(method) << VAR(duration);
181+
};
182+
183+
for (auto& [method, unit] : units) {
184+
LogInfo << "Warming up" << method;
185+
if (!unit->screencap()) {
186+
LogWarn << "failed to warm up" << method;
187+
}
188+
189+
LogInfo << "Testing" << method;
190+
auto now = std::chrono::steady_clock::now();
191+
if (!unit->screencap()) {
192+
LogWarn << "failed to test" << method;
193+
continue;
194+
}
195+
check(method, now);
196+
}
197+
198+
if (fastest == ScreencapMethod::UnknownYet) {
199+
LogError << "cannot find any method to screencap!";
200+
return nullptr;
201+
}
202+
203+
LogInfo << "The fastest method is" << fastest << VAR(cost);
204+
return units.at(fastest);
205+
}
206+
140207
bool Win32ControlUnitMgr::connected() const
141208
{
142209
// 除了检查连接标志,还需要检查窗口是否仍然有效

source/MaaWin32ControlUnit/Manager/Win32ControlUnitMgr.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#pragma once
22

33
#include <filesystem>
4+
#include <ostream>
5+
#include <unordered_map>
46

57
#include "Base/UnitBase.h"
68
#include "ControlUnit/ControlUnitAPI.h"
@@ -52,6 +54,24 @@ class Win32ControlUnitMgr : public Win32ControlUnitAPI
5254

5355
virtual json::object get_info() const override;
5456

57+
private:
58+
enum class ScreencapMethod
59+
{
60+
UnknownYet,
61+
GDI,
62+
FramePool,
63+
DXGI_DesktopDup,
64+
DXGI_DesktopDup_Window,
65+
PrintWindow,
66+
ScreenDC,
67+
};
68+
69+
std::unordered_map<ScreencapMethod, std::shared_ptr<ScreencapBase>> build_screencap_units() const;
70+
bool init_screencap();
71+
std::shared_ptr<InputBase> make_input(MaaWin32InputMethod method) const;
72+
73+
std::shared_ptr<ScreencapBase> speed_test(const std::unordered_map<ScreencapMethod, std::shared_ptr<ScreencapBase>>& units) const;
74+
5575
private:
5676
HWND hwnd_ = nullptr;
5777
MaaWin32ScreencapMethod screencap_method_ = MaaWin32ScreencapMethod_None;

source/binding/NodeJS/src/apis/constant.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ static maajs::ValueType load_win32_screencap_method(maajs::EnvType env)
6161
DEM(MaaWin32ScreencapMethod, DXGI_DesktopDup_Window);
6262
DEM(MaaWin32ScreencapMethod, PrintWindow);
6363
DEM(MaaWin32ScreencapMethod, ScreenDC);
64+
DEM(MaaWin32ScreencapMethod, All);
65+
DEM(MaaWin32ScreencapMethod, Foreground);
66+
DEM(MaaWin32ScreencapMethod, Background);
6467

6568
return obj;
6669
}

source/binding/NodeJS/src/apis/constant.d.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,17 @@ declare global {
5959
>
6060

6161
/**
62-
* Win32 screencap method.
62+
* Win32 screencap method flags.
6363
*
64-
* No bitwise OR, select ONE method only.
64+
* Use bitwise OR to set the methods you need.
65+
* MaaFramework will test all provided methods and use the fastest available one.
6566
*
6667
* No default value. Client should choose one as default.
6768
*
69+
* Predefined combinations:
70+
* - Foreground: DXGI_DesktopDup_Window | ScreenDC
71+
* - Background: FramePool | PrintWindow
72+
*
6873
* Different applications use different rendering methods, there is no universal solution.
6974
*
7075
* | Method | Speed | Compatibility | Require Admin | Background Support | Notes |
@@ -87,7 +92,10 @@ declare global {
8792
| 'DXGI_DesktopDup'
8893
| 'DXGI_DesktopDup_Window'
8994
| 'PrintWindow'
90-
| 'ScreenDC',
95+
| 'ScreenDC'
96+
| 'All'
97+
| 'Foreground'
98+
| 'Background',
9199
ScreencapOrInputMethods
92100
>
93101

0 commit comments

Comments
 (0)