diff --git a/bindings/go/dll_windows.go b/bindings/go/dll_windows.go index c216ca0..f663c1a 100644 --- a/bindings/go/dll_windows.go +++ b/bindings/go/dll_windows.go @@ -88,11 +88,23 @@ var ( procLeftDown *windows.LazyProc procLeftUp *windows.LazyProc procMiddleClick *windows.LazyProc + procMiddleDoubleClick *windows.LazyProc procMiddleDown *windows.LazyProc procMiddleUp *windows.LazyProc procRightClick *windows.LazyProc + procRightDoubleClick *windows.LazyProc procRightDown *windows.LazyProc procRightUp *windows.LazyProc + procXButton1Click *windows.LazyProc + procXButton1DoubleClick *windows.LazyProc + procXButton1Down *windows.LazyProc + procXButton1Up *windows.LazyProc + procXButton2Click *windows.LazyProc + procXButton2DoubleClick *windows.LazyProc + procXButton2Down *windows.LazyProc + procXButton2Up *windows.LazyProc + procWheel *windows.LazyProc + procHWheel *windows.LazyProc procWheelDown *windows.LazyProc procWheelUp *windows.LazyProc procSetMouseDelay *windows.LazyProc @@ -286,11 +298,23 @@ func bindProcs() { procLeftDown = dll.NewProc("OpLeftDown") procLeftUp = dll.NewProc("OpLeftUp") procMiddleClick = dll.NewProc("OpMiddleClick") + procMiddleDoubleClick = dll.NewProc("OpMiddleDoubleClick") procMiddleDown = dll.NewProc("OpMiddleDown") procMiddleUp = dll.NewProc("OpMiddleUp") procRightClick = dll.NewProc("OpRightClick") + procRightDoubleClick = dll.NewProc("OpRightDoubleClick") procRightDown = dll.NewProc("OpRightDown") procRightUp = dll.NewProc("OpRightUp") + procXButton1Click = dll.NewProc("OpXButton1Click") + procXButton1DoubleClick = dll.NewProc("OpXButton1DoubleClick") + procXButton1Down = dll.NewProc("OpXButton1Down") + procXButton1Up = dll.NewProc("OpXButton1Up") + procXButton2Click = dll.NewProc("OpXButton2Click") + procXButton2DoubleClick = dll.NewProc("OpXButton2DoubleClick") + procXButton2Down = dll.NewProc("OpXButton2Down") + procXButton2Up = dll.NewProc("OpXButton2Up") + procWheel = dll.NewProc("OpWheel") + procHWheel = dll.NewProc("OpHWheel") procWheelDown = dll.NewProc("OpWheelDown") procWheelUp = dll.NewProc("OpWheelUp") procSetMouseDelay = dll.NewProc("OpSetMouseDelay") diff --git a/bindings/go/input_windows.go b/bindings/go/input_windows.go index 72a70c2..d4feaf5 100644 --- a/bindings/go/input_windows.go +++ b/bindings/go/input_windows.go @@ -68,6 +68,10 @@ func (o *Op) MiddleClick() int { return o.callNoArgs(procMiddleClick) } +func (o *Op) MiddleDoubleClick() int { + return o.callNoArgs(procMiddleDoubleClick) +} + func (o *Op) MiddleDown() int { return o.callNoArgs(procMiddleDown) } @@ -80,6 +84,10 @@ func (o *Op) RightClick() int { return o.callNoArgs(procRightClick) } +func (o *Op) RightDoubleClick() int { + return o.callNoArgs(procRightDoubleClick) +} + func (o *Op) RightDown() int { return o.callNoArgs(procRightDown) } @@ -88,6 +96,56 @@ func (o *Op) RightUp() int { return o.callNoArgs(procRightUp) } +func (o *Op) XButton1Click() int { + return o.callNoArgs(procXButton1Click) +} + +func (o *Op) XButton1DoubleClick() int { + return o.callNoArgs(procXButton1DoubleClick) +} + +func (o *Op) XButton1Down() int { + return o.callNoArgs(procXButton1Down) +} + +func (o *Op) XButton1Up() int { + return o.callNoArgs(procXButton1Up) +} + +func (o *Op) XButton2Click() int { + return o.callNoArgs(procXButton2Click) +} + +func (o *Op) XButton2DoubleClick() int { + return o.callNoArgs(procXButton2DoubleClick) +} + +func (o *Op) XButton2Down() int { + return o.callNoArgs(procXButton2Down) +} + +func (o *Op) XButton2Up() int { + return o.callNoArgs(procXButton2Up) +} + +func (o *Op) Wheel(delta int) int { + if !o.valid() { + return 0 + } + + ret, _, _ := procWheel.Call(o.handle, uintptr(delta)) + return int(ret) +} + +func (o *Op) HWheel(delta int) int { + if !o.valid() { + return 0 + } + + ret, _, _ := procHWheel.Call(o.handle, uintptr(delta)) + return int(ret) +} + func (o *Op) WheelDown() int { return o.callNoArgs(procWheelDown) } diff --git a/bindings/python/op/_ffi.py b/bindings/python/op/_ffi.py index 8fa4c20..b92a0c8 100644 --- a/bindings/python/op/_ffi.py +++ b/bindings/python/op/_ffi.py @@ -188,11 +188,23 @@ def _bind(dll: ctypes.WinDLL) -> ctypes.WinDLL: ("OpLeftDown", c_int, [op_handle]), ("OpLeftUp", c_int, [op_handle]), ("OpMiddleClick", c_int, [op_handle]), + ("OpMiddleDoubleClick", c_int, [op_handle]), ("OpMiddleDown", c_int, [op_handle]), ("OpMiddleUp", c_int, [op_handle]), ("OpRightClick", c_int, [op_handle]), + ("OpRightDoubleClick", c_int, [op_handle]), ("OpRightDown", c_int, [op_handle]), ("OpRightUp", c_int, [op_handle]), + ("OpXButton1Click", c_int, [op_handle]), + ("OpXButton1DoubleClick", c_int, [op_handle]), + ("OpXButton1Down", c_int, [op_handle]), + ("OpXButton1Up", c_int, [op_handle]), + ("OpXButton2Click", c_int, [op_handle]), + ("OpXButton2DoubleClick", c_int, [op_handle]), + ("OpXButton2Down", c_int, [op_handle]), + ("OpXButton2Up", c_int, [op_handle]), + ("OpWheel", c_int, [op_handle, c_int]), + ("OpHWheel", c_int, [op_handle, c_int]), ("OpWheelDown", c_int, [op_handle]), ("OpWheelUp", c_int, [op_handle]), ("OpSetMouseDelay", c_int, [op_handle, c_wchar_p, c_int]), diff --git a/bindings/python/op/api.py b/bindings/python/op/api.py index b804cd5..1f5d729 100644 --- a/bindings/python/op/api.py +++ b/bindings/python/op/api.py @@ -433,6 +433,9 @@ def left_up(self) -> bool: def middle_click(self) -> bool: return self._call_ok("OpMiddleClick") + def middle_double_click(self) -> bool: + return self._call_ok("OpMiddleDoubleClick") + def middle_down(self) -> bool: return self._call_ok("OpMiddleDown") @@ -442,12 +445,45 @@ def middle_up(self) -> bool: def right_click(self) -> bool: return self._call_ok("OpRightClick") + def right_double_click(self) -> bool: + return self._call_ok("OpRightDoubleClick") + def right_down(self) -> bool: return self._call_ok("OpRightDown") def right_up(self) -> bool: return self._call_ok("OpRightUp") + def xbutton1_click(self) -> bool: + return self._call_ok("OpXButton1Click") + + def xbutton1_double_click(self) -> bool: + return self._call_ok("OpXButton1DoubleClick") + + def xbutton1_down(self) -> bool: + return self._call_ok("OpXButton1Down") + + def xbutton1_up(self) -> bool: + return self._call_ok("OpXButton1Up") + + def xbutton2_click(self) -> bool: + return self._call_ok("OpXButton2Click") + + def xbutton2_double_click(self) -> bool: + return self._call_ok("OpXButton2DoubleClick") + + def xbutton2_down(self) -> bool: + return self._call_ok("OpXButton2Down") + + def xbutton2_up(self) -> bool: + return self._call_ok("OpXButton2Up") + + def wheel(self, delta: int) -> bool: + return self._call_ok("OpWheel", int(delta)) + + def hwheel(self, delta: int) -> bool: + return self._call_ok("OpHWheel", int(delta)) + def wheel_down(self) -> bool: return self._call_ok("OpWheelDown") diff --git a/include/libop.h b/include/libop.h index e5d9d9e..e744721 100644 --- a/include/libop.h +++ b/include/libop.h @@ -250,17 +250,41 @@ class OP_API Client { // 弹起鼠标左键 void LeftUp(_Out_ long *ret); // 按下鼠标中键 - void MiddleClick(_Out_ long *ret); + void MiddleClick(long *ret); + // 双击鼠标中键 + void MiddleDoubleClick(long *ret); // 按住鼠标中键 void MiddleDown(_Out_ long *ret); // 弹起鼠标中键 void MiddleUp(_Out_ long *ret); // 按下鼠标右键 - void RightClick(_Out_ long *ret); + void RightClick(long *ret); + // 双击鼠标右键 + void RightDoubleClick(long *ret); // 按住鼠标右键 void RightDown(_Out_ long *ret); // 弹起鼠标右键 - void RightUp(_Out_ long *ret); + void RightUp(long *ret); + // 按下鼠标侧键1 + void XButton1Click(long *ret); + // 双击鼠标侧键1 + void XButton1DoubleClick(long *ret); + // 按住鼠标侧键1 + void XButton1Down(long *ret); + // 弹起鼠标侧键1 + void XButton1Up(long *ret); + // 按下鼠标侧键2 + void XButton2Click(long *ret); + // 双击鼠标侧键2 + void XButton2DoubleClick(long *ret); + // 按住鼠标侧键2 + void XButton2Down(long *ret); + // 弹起鼠标侧键2 + void XButton2Up(long *ret); + // 垂直滚轮滚动指定 delta + void Wheel(long delta, long *ret); + // 水平滚轮滚动指定 delta + void HWheel(long delta, long *ret); // 滚轮向下滚 void WheelDown(_Out_ long *ret); // 滚轮向上滚 diff --git a/include/op_c_api.h b/include/op_c_api.h index 96be3df..e99233b 100644 --- a/include/op_c_api.h +++ b/include/op_c_api.h @@ -124,11 +124,23 @@ OP_C_API int OP_CALL OpLeftDoubleClick(op_handle handle); OP_C_API int OP_CALL OpLeftDown(op_handle handle); OP_C_API int OP_CALL OpLeftUp(op_handle handle); OP_C_API int OP_CALL OpMiddleClick(op_handle handle); +OP_C_API int OP_CALL OpMiddleDoubleClick(op_handle handle); OP_C_API int OP_CALL OpMiddleDown(op_handle handle); OP_C_API int OP_CALL OpMiddleUp(op_handle handle); OP_C_API int OP_CALL OpRightClick(op_handle handle); +OP_C_API int OP_CALL OpRightDoubleClick(op_handle handle); OP_C_API int OP_CALL OpRightDown(op_handle handle); OP_C_API int OP_CALL OpRightUp(op_handle handle); +OP_C_API int OP_CALL OpXButton1Click(op_handle handle); +OP_C_API int OP_CALL OpXButton1DoubleClick(op_handle handle); +OP_C_API int OP_CALL OpXButton1Down(op_handle handle); +OP_C_API int OP_CALL OpXButton1Up(op_handle handle); +OP_C_API int OP_CALL OpXButton2Click(op_handle handle); +OP_C_API int OP_CALL OpXButton2DoubleClick(op_handle handle); +OP_C_API int OP_CALL OpXButton2Down(op_handle handle); +OP_C_API int OP_CALL OpXButton2Up(op_handle handle); +OP_C_API int OP_CALL OpWheel(op_handle handle, int delta); +OP_C_API int OP_CALL OpHWheel(op_handle handle, int delta); OP_C_API int OP_CALL OpWheelDown(op_handle handle); OP_C_API int OP_CALL OpWheelUp(op_handle handle); OP_C_API int OP_CALL OpSetMouseDelay(op_handle handle, const wchar_t *type, int delay); diff --git a/libop/CMakeLists.txt b/libop/CMakeLists.txt index 9bfd0aa..f48786f 100644 --- a/libop/CMakeLists.txt +++ b/libop/CMakeLists.txt @@ -273,6 +273,8 @@ set(OP_COM_SOURCES set(OP_C_API_SOURCES "c_api/op_c_api.cpp" + "hook/HookExport.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/c_api/op_c_api.rc" ) set(OP_COMMON_DEFINITIONS @@ -283,7 +285,7 @@ set(OP_COMMON_DEFINITIONS _SILENCE_CXX17_ADAPTOR_TYPEDEFS_DEPRECATION_WARNING _UNICODE UNICODE - OP_VERSION="0.4.7.0" + OP_VERSION="0.4.8.1" ) if(DEFINED OP_ARCH_DEFINITION AND NOT OP_ARCH_DEFINITION STREQUAL "") list(APPEND OP_COMMON_DEFINITIONS "${OP_ARCH_DEFINITION}") diff --git a/libop/binding/BindingSession.cpp b/libop/binding/BindingSession.cpp index 5409cc1..bd4eef0 100644 --- a/libop/binding/BindingSession.cpp +++ b/libop/binding/BindingSession.cpp @@ -543,21 +543,21 @@ bool BindingSession::requestCapture(int x1, int y1, int w, int h, Image &img) { return false; } -std::unique_ptr BindingSession::createDisplay(int mode) { +std::shared_ptr BindingSession::createDisplay(int mode) { if (mode == RDT_NORMAL || GET_RENDER_TYPE(mode) == RENDER_TYPE::GDI) { - return std::make_unique(); + return std::make_shared(); } else if (mode == RDT_NORMAL_DXGI) { - return std::make_unique(); + return std::make_shared(); } #ifdef OP_ENABLE_WGC else if (mode == RDT_NORMAL_WGC) { - return std::make_unique(); + return std::make_shared(); } #endif else if (GET_RENDER_TYPE(mode) == RENDER_TYPE::DX) { - return std::make_unique(); + return std::make_shared(); } else if (GET_RENDER_TYPE(mode) == RENDER_TYPE::OPENGL) - return std::make_unique(); + return std::make_shared(); return nullptr; } diff --git a/libop/binding/BindingSession.h b/libop/binding/BindingSession.h index c3eb2f5..efaa444 100644 --- a/libop/binding/BindingSession.h +++ b/libop/binding/BindingSession.h @@ -57,12 +57,12 @@ class BindingSession { Image _pic; long reset_bind_state(bool restore_default_input); - std::unique_ptr createDisplay(int mode); + std::shared_ptr createDisplay(int mode); std::unique_ptr createMouse(int mode); std::unique_ptr createKeypad(int mode); public: - std::unique_ptr _capture; + std::shared_ptr _capture; std::unique_ptr _mouse; std::unique_ptr _keyboard; wstring _curr_path; diff --git a/libop/c_api/op_c_api.cpp b/libop/c_api/op_c_api.cpp index 0463a70..e435d49 100644 --- a/libop/c_api/op_c_api.cpp +++ b/libop/c_api/op_c_api.cpp @@ -2,6 +2,7 @@ #include "../../include/libop.h" #include "../memory/ProcessMemory.h" +#include "../runtime/RuntimeEnvironment.h" #include #include @@ -12,6 +13,13 @@ #undef FindWindowEx #undef SetWindowText +extern "C" BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD reason, LPVOID) { + if (reason == DLL_PROCESS_ATTACH) { + RuntimeEnvironment::setInstance(hInstance); + } + return TRUE; +} + #define OP_WIDEN_TEXT2(text) L##text #define OP_WIDEN_TEXT(text) OP_WIDEN_TEXT2(text) @@ -515,16 +523,34 @@ OP_MOUSE_RET(OpLeftDoubleClick, LeftDoubleClick) OP_MOUSE_RET(OpLeftDown, LeftDown) OP_MOUSE_RET(OpLeftUp, LeftUp) OP_MOUSE_RET(OpMiddleClick, MiddleClick) +OP_MOUSE_RET(OpMiddleDoubleClick, MiddleDoubleClick) OP_MOUSE_RET(OpMiddleDown, MiddleDown) OP_MOUSE_RET(OpMiddleUp, MiddleUp) OP_MOUSE_RET(OpRightClick, RightClick) +OP_MOUSE_RET(OpRightDoubleClick, RightDoubleClick) OP_MOUSE_RET(OpRightDown, RightDown) OP_MOUSE_RET(OpRightUp, RightUp) +OP_MOUSE_RET(OpXButton1Click, XButton1Click) +OP_MOUSE_RET(OpXButton1DoubleClick, XButton1DoubleClick) +OP_MOUSE_RET(OpXButton1Down, XButton1Down) +OP_MOUSE_RET(OpXButton1Up, XButton1Up) +OP_MOUSE_RET(OpXButton2Click, XButton2Click) +OP_MOUSE_RET(OpXButton2DoubleClick, XButton2DoubleClick) +OP_MOUSE_RET(OpXButton2Down, XButton2Down) +OP_MOUSE_RET(OpXButton2Up, XButton2Up) OP_MOUSE_RET(OpWheelDown, WheelDown) OP_MOUSE_RET(OpWheelUp, WheelUp) #undef OP_MOUSE_RET +int OP_CALL OpWheel(op_handle handle, int delta) { + return call_ret(handle, [&](op::Client &op, long *ret) { op.Wheel(delta, ret); }); +} + +int OP_CALL OpHWheel(op_handle handle, int delta) { + return call_ret(handle, [&](op::Client &op, long *ret) { op.HWheel(delta, ret); }); +} + int OP_CALL OpSetMouseDelay(op_handle handle, const wchar_t *type, int delay) { return call_ret(handle, [&](op::Client &op, long *ret) { op.SetMouseDelay(safe_text(type), delay, ret); }); } diff --git a/libop/c_api/op_c_api.rc b/libop/c_api/op_c_api.rc new file mode 100644 index 0000000..f8e6301 --- /dev/null +++ b/libop/c_api/op_c_api.rc @@ -0,0 +1,38 @@ +// Version resource for the C API DLL. +// +#include "winres.h" +#include "verrsrc.h" + +LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,4,8,1 + PRODUCTVERSION 0,4,8,1 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "080404B0" + BEGIN + VALUE "CompanyName", "op" + VALUE "FileDescription", "op C API DLL" + VALUE "FileVersion", "0.4.8.1" + VALUE "InternalName", "op_c_api.dll" + VALUE "OriginalFilename", "op_c_api.dll" + VALUE "ProductName", "op" + VALUE "ProductVersion", "0.4.8.1" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x804, 1200 + END +END diff --git a/libop/capture/ICaptureBackend.cpp b/libop/capture/ICaptureBackend.cpp index 813e380..1922aa1 100644 --- a/libop/capture/ICaptureBackend.cpp +++ b/libop/capture/ICaptureBackend.cpp @@ -45,6 +45,9 @@ long ICaptureBackend::UnBind() { if (_bind_state) { UnBindEx(); } + if (DeferBindReleaseAfterUnBind()) { + return 1; + } bind_release(); _bind_state = 0; return 1; diff --git a/libop/capture/ICaptureBackend.h b/libop/capture/ICaptureBackend.h index a8a617b..68fe58e 100644 --- a/libop/capture/ICaptureBackend.h +++ b/libop/capture/ICaptureBackend.h @@ -4,6 +4,7 @@ #include "../ipc/SharedMemory.h" #include "FrameInfo.h" #include +#include #include namespace op { @@ -12,7 +13,7 @@ struct Image; namespace op::capture { -class ICaptureBackend { +class ICaptureBackend : public std::enable_shared_from_this { public: ICaptureBackend(); virtual ~ICaptureBackend(); @@ -62,6 +63,9 @@ class ICaptureBackend { protected: virtual long BindEx(HWND hwnd, long flag) = 0; virtual long UnBindEx() = 0; + virtual bool DeferBindReleaseAfterUnBind() const { + return false; + } HWND _hwnd; diff --git a/libop/capture/backends/DxgiCapture.cpp b/libop/capture/backends/DxgiCapture.cpp index 61ffdc2..8018157 100644 --- a/libop/capture/backends/DxgiCapture.cpp +++ b/libop/capture/backends/DxgiCapture.cpp @@ -4,6 +4,7 @@ #include "../../image/Image.h" #include "../../runtime/AutomationModes.h" #include "../../runtime/RuntimeUtils.h" +#include #include #include #include @@ -34,11 +35,11 @@ class DxgiFrameLease { DxgiFrameLease(const DxgiFrameLease &) = delete; DxgiFrameLease &operator=(const DxgiFrameLease &) = delete; - HRESULT acquire(DXGI_OUTDUPL_FRAME_INFO *frame_info, IDXGIResource **resource) { + HRESULT acquire(UINT timeout_ms, DXGI_OUTDUPL_FRAME_INFO *frame_info, IDXGIResource **resource) { if (!duplication_ || !frame_info || !resource) { return E_POINTER; } - HRESULT hr = duplication_->AcquireNextFrame(0, frame_info, resource); + HRESULT hr = duplication_->AcquireNextFrame(timeout_ms, frame_info, resource); if (SUCCEEDED(hr)) { acquired_ = true; } @@ -126,14 +127,19 @@ long DxgiCapture::UnBindEx() { void DxgiCapture::waitForBindReady() { const unsigned long long deadline = ::GetTickCount64() + 1000; + int fresh_frames = 0; do { ID3D11Texture2D *texture_raw = nullptr; - // DXGI 首帧也是异步到达的,绑定后预热一帧,避免 normal.auto 立即截图拿到黑图。 - if (GetDesktopFrame(&texture_raw)) { + bool fresh_frame = false; + // 部分系统/驱动刚创建 duplication 后的第一个 fresh frame 可能还没包含目标窗口内容; + // 这里靠帧序推进预热到下一帧,而不是用像素颜色猜测黑帧。 + if (GetDesktopFrame(&texture_raw, 50, &fresh_frame)) { if (texture_raw) { texture_raw->Release(); } - return; + if (fresh_frame && ++fresh_frames >= 2) { + return; + } } if (duplication_lost_ && !RebuildDuplication()) { @@ -151,7 +157,6 @@ bool DxgiCapture::requestCapture(int x1, int y1, int w, int h, Image &img) { } } - img.create(w, h); ID3D11Texture2D *texture_raw = nullptr; if (!GetDesktopFrame(&texture_raw)) { if (duplication_lost_ && RebuildDuplication() && GetDesktopFrame(&texture_raw)) { @@ -168,26 +173,51 @@ bool DxgiCapture::requestCapture(int x1, int y1, int w, int h, Image &img) { return false; } - texture2D->GetDesc(&m_desc); + if (!copyTextureToImage(texture2D, x1, y1, w, h, img)) { + return false; + } + + return true; +} + +bool DxgiCapture::copyTextureToImage(ID3D11Texture2D *texture, int x1, int y1, int w, int h, Image &img) { + if (!texture) { + return false; + } + + texture->GetDesc(&m_desc); // DXGI 输出纹理使用当前显示器坐标系,窗口客户区需要先转成输出内坐标。 - int src_x = x1 + client_screen_origin_.x - output_rect_.left; - int src_y = y1 + client_screen_origin_.y - output_rect_.top; - if (src_x < 0 || src_y < 0 || src_x + w > static_cast(m_desc.Width) || - src_y + h > static_cast(m_desc.Height)) { - setlog("error w and h src_x=%d,w=%d,desc.Width=%d,src_y=%d,h=%d,desc.Height=%d", src_x, w, m_desc.Width, src_y, - h, m_desc.Height); + // 最大化窗口的客户区有时会被 DWM 报成略微越过显示器边界,例如 (-1,-1,1922,1022)。 + // DXGI 纹理只包含真实输出区域,越界部分没有可读像素;这里夹取相交区域,保持返回图尺寸不变。 + const int src_x = x1 + client_screen_origin_.x - output_rect_.left; + const int src_y = y1 + client_screen_origin_.y - output_rect_.top; + const int desc_width = static_cast(m_desc.Width); + const int desc_height = static_cast(m_desc.Height); + const int copy_src_x = std::clamp(src_x, 0, desc_width); + const int copy_src_y = std::clamp(src_y, 0, desc_height); + const int copy_src_right = std::clamp(src_x + w, 0, desc_width); + const int copy_src_bottom = std::clamp(src_y + h, 0, desc_height); + const int copy_w = copy_src_right - copy_src_x; + const int copy_h = copy_src_bottom - copy_src_y; + if (copy_w <= 0 || copy_h <= 0) { + setlog("DXGI capture rectangle outside output src_x=%d,w=%d,desc.Width=%d,src_y=%d,h=%d,desc.Height=%d", src_x, + w, m_desc.Width, src_y, h, m_desc.Height); return false; } + const int dst_x = copy_src_x - src_x; + const int dst_y = copy_src_y - src_y; D3D11_MAPPED_SUBRESOURCE mappedResource = {}; - D3D11TextureMap mappedTexture(deviceContext_, texture2D); + D3D11TextureMap mappedTexture(deviceContext_, texture); HRESULT hr = mappedTexture.map(&mappedResource); if (FAILED(hr)) { setlog("Map desktop frame failed hr=0x%08X", hr); return false; } + img.create(w, h); + img.fill(0xff000000); uint8_t *pData = static_cast(mappedResource.pData); if (_pmutex && _shmem) { _pmutex->lock(); @@ -195,8 +225,9 @@ bool DxgiCapture::requestCapture(int x1, int y1, int w, int h, Image &img) { _pmutex->unlock(); } - for (int i = 0; i < h; i++) { - memcpy(img.ptr(i), pData + (src_y + i) * mappedResource.RowPitch + src_x * 4, 4 * w); + for (int i = 0; i < copy_h; i++) { + memcpy(img.ptr(dst_y + i) + dst_x * 4, + pData + (copy_src_y + i) * mappedResource.RowPitch + copy_src_x * 4, 4 * copy_w); } return true; } @@ -345,16 +376,19 @@ bool DxgiCapture::InitDuplication() { return false; } -bool DxgiCapture::GetDesktopFrame(ID3D11Texture2D **texture) { +bool DxgiCapture::GetDesktopFrame(ID3D11Texture2D **texture, DWORD timeout_ms, bool *fresh_frame) { if (!set_out(texture, nullptr)) return false; + if (fresh_frame) { + *fresh_frame = false; + } HRESULT hr = S_OK; DXGI_OUTDUPL_FRAME_INFO frameInfo; CComPtr resource; CComPtr acquireFrame; DxgiFrameLease frameLease(duplication_); - hr = frameLease.acquire(&frameInfo, &resource); + hr = frameLease.acquire(timeout_ms, &frameInfo, &resource); if (FAILED(hr)) { if (hr == DXGI_ERROR_WAIT_TIMEOUT) { // 没有新桌面帧时复用上一帧,避免静态画面被当作捕获失败。 @@ -397,6 +431,9 @@ bool DxgiCapture::GetDesktopFrame(ID3D11Texture2D **texture) { } lastTexture_ = copyTexture; + if (fresh_frame) { + *fresh_frame = true; + } return SUCCEEDED(lastTexture_.CopyTo(texture)); } diff --git a/libop/capture/backends/DxgiCapture.h b/libop/capture/backends/DxgiCapture.h index b6cf32d..1fdbdad 100644 --- a/libop/capture/backends/DxgiCapture.h +++ b/libop/capture/backends/DxgiCapture.h @@ -27,7 +27,7 @@ class DxgiCapture : public ICaptureBackend { bool InitDuplication(); - bool GetDesktopFrame(ID3D11Texture2D **texture); + bool GetDesktopFrame(ID3D11Texture2D **texture, DWORD timeout_ms = 0, bool *fresh_frame = nullptr); private: ATL::CComPtr device_; @@ -43,6 +43,7 @@ class DxgiCapture : public ICaptureBackend { D3D11_TEXTURE2D_DESC m_desc{}; bool refreshWindowMetrics(); bool RebuildDuplication(); + bool copyTextureToImage(ID3D11Texture2D *texture, int x1, int y1, int w, int h, Image &img); void fmtFrameInfo(void *dst, HWND hwnd, int w, int h, bool inc = true); }; diff --git a/libop/capture/backends/HookCapture.cpp b/libop/capture/backends/HookCapture.cpp index c2e98ff..2c71ff4 100644 --- a/libop/capture/backends/HookCapture.cpp +++ b/libop/capture/backends/HookCapture.cpp @@ -5,6 +5,7 @@ #include "../../runtime/AutomationModes.h" #include "../../runtime/RuntimeEnvironment.h" #include "../../runtime/RuntimeUtils.h" +#include "../../hook/HookModule.h" #include "BlackBone/Process/Process.h" #include "BlackBone/Process/RPC/RemoteFunction.hpp" #include @@ -88,12 +89,8 @@ long HookCapture::BindEx(HWND hwnd, long render_type) { hr = proc.Attach(id); if (NT_SUCCESS(hr)) { - wstring dllname = RuntimeEnvironment::getOpName(); - // 检查是否与插件相同的32/64位,如果不同,则使用另一种dll BOOL is64 = proc.modules().GetMainModule()->type == blackbone::eModType::mt_mod64; - if (is64 != OP64) { - dllname = is64 ? L"op_x64.dll" : L"op_x86.dll"; - } + wstring dllname = op::hook::ResolveHookModuleName(is64 != FALSE); bool injected = false; // 判断是否已经注入 @@ -125,7 +122,7 @@ long HookCapture::BindEx(HWND hwnd, long render_type) { bind_ret = cret.result(); // setlog("after result"); } else { - // setlog(L"remote function not found."); + setlog(L"remote function 'SetDisplayHook' not found in %s.", dllname.c_str()); } } else { setlog(L"Inject false."); @@ -162,12 +159,8 @@ long HookCapture::UnBindEx() { hr = proc.Attach(id); if (NT_SUCCESS(hr)) { - wstring dllname = RuntimeEnvironment::getOpName(); - // 检查是否与插件相同的32/64位,如果不同,则使用另一种dll BOOL is64 = proc.modules().GetMainModule()->type == blackbone::eModType::mt_mod64; - if (is64 != OP64) { - dllname = is64 ? L"op_x64.dll" : L"op_x86.dll"; - } + wstring dllname = op::hook::ResolveHookModuleName(is64 != FALSE); // setlog(L"bkdo::dllname=%s",dllname); using my_func_t = long(__stdcall *)(void); auto pUnXHook = blackbone::MakeRemoteFunction(proc, dllname, "ReleaseDisplayHook"); diff --git a/libop/capture/backends/WgcCapture.cpp b/libop/capture/backends/WgcCapture.cpp index 7a74b99..221a13a 100644 --- a/libop/capture/backends/WgcCapture.cpp +++ b/libop/capture/backends/WgcCapture.cpp @@ -9,7 +9,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -72,25 +74,78 @@ bool wgcSessionPropertyPresent(const wchar_t *property_name) { } } -// 用 WinRT API 特性探测替代 Windows build 号判断,可靠地关闭黄框与光标, -// 避免它们污染找色/找图/OCR 的像素。Init 与 restartCaptureSession 共用。 +template Result runInMtaApartment(Func func, Result fallback) { + Result result = fallback; + bool apartment_initialized = false; + try { + winrt::init_apartment(winrt::apartment_type::multi_threaded); + apartment_initialized = true; + result = func(); + } catch (winrt::hresult_error &err) { + setlog("WGC worker thread (0x%08X): %s", err.code().value, winrt::to_string(err.message()).c_str()); + } catch (...) { + setlog("WGC worker thread (0x%08X)", winrt::to_hresult().value); + } + + if (apartment_initialized) { + try { + winrt::uninit_apartment(); + } catch (...) { + } + } + return result; +} + +bool shouldUseWgcWorkerThread() { + return !IsWindows10BuildOrGreater(kWindows11Build22000); +} + +constexpr unsigned long kWin10RequestInitWaitMs = 300; +constexpr unsigned long kWin10InitialFrameWaitMs = 500; +constexpr unsigned long kWin10MetricsFrameWaitMs = 800; +constexpr unsigned long kWin10FreshFrameWaitMs = 100; +constexpr unsigned long kWin10CloseWaitMs = 1500; +constexpr unsigned long kWin10CleanupBindWaitMs = 200; +std::atomic g_win10WgcCleanupInFlight{0}; + +bool waitForWin10WgcCleanup(unsigned long timeout_ms) { + const unsigned long long deadline = ::GetTickCount64() + timeout_ms; + while (g_win10WgcCleanupInFlight.load() > 0) { + if (::GetTickCount64() >= deadline) { + return false; + } + ::Sleep(10); + } + return true; +} + +void debugWgcCloseMessage(const char *message) { + ::OutputDebugStringA(message); + ::OutputDebugStringA("\n"); +} + +void debugWgcCloseHresult(const char *operation, winrt::hresult_error &err) { + char buffer[512] = {}; + std::snprintf(buffer, sizeof(buffer), "%s (0x%08X): %s", operation, err.code().value, + winrt::to_string(err.message()).c_str()); + debugWgcCloseMessage(buffer); +} + +// Windows 10 this runs in the WGC worker thread, so a slow Borderless access +// request does not block the caller's BindWindow thread. void applySessionOptions(const winrt::Windows::Graphics::Capture::GraphicsCaptureSession &session) { try { if (wgcSessionPropertyPresent(L"IsBorderRequired")) { - // 关黄框前先申请 Borderless 访问,确保部分 Windows 版本上 IsBorderRequired(false) 真正生效。 - // 这里不要同步 .get()。在部分宿主线程/窗口模型下,这个异步请求可能一直不返回, - // 从而把 BindWindow("normal.wgc", ...) 整个卡死。这里退化成纯 best-effort: - // 发起请求即可,真正能否隐藏黄框不影响 WGC 捕获主流程。 - try { - const auto access_request = - winrt::Windows::Graphics::Capture::GraphicsCaptureAccess::RequestAccessAsync( - winrt::Windows::Graphics::Capture::GraphicsCaptureAccessKind::Borderless); - (void)access_request; - } catch (...) { - } + winrt::Windows::Graphics::Capture::GraphicsCaptureAccess::RequestAccessAsync( + winrt::Windows::Graphics::Capture::GraphicsCaptureAccessKind::Borderless) + .get(); session.IsBorderRequired(false); } + } catch (winrt::hresult_error &err) { + setlog("Request WGC borderless access failed (0x%08X): %s", err.code().value, + winrt::to_string(err.message()).c_str()); } catch (...) { + setlog("Request WGC borderless access failed (0x%08X)", winrt::to_hresult().value); } try { if (wgcSessionPropertyPresent(L"IsCursorCaptureEnabled")) { @@ -105,10 +160,16 @@ void applySessionOptions(const winrt::Windows::Graphics::Capture::GraphicsCaptur WgcCapture::WgcCapture() = default; WgcCapture::~WgcCapture() { + captureStopping_.store(true); UnBindEx(); } long WgcCapture::BindEx(HWND _hwnd, long render_type) { + (void)render_type; + if (shouldUseWgcWorkerThread()) { + return BindExOnWindows10(_hwnd); + } + if (!Init(_hwnd)) { setlog("Init wgc failed"); return 0; @@ -117,6 +178,47 @@ long WgcCapture::BindEx(HWND _hwnd, long render_type) { } long WgcCapture::UnBindEx() { + if (shouldUseWgcWorkerThread()) { + return UnBindExOnWindows10(); + } + return UnBindExInternal(); +} + +bool WgcCapture::DeferBindReleaseAfterUnBind() const { + return shouldUseWgcWorkerThread() && win10WorkerRunning_.load(); +} + +long WgcCapture::BindExOnWindows10(HWND hwnd) { + if (!waitForWin10WgcCleanup(kWin10CleanupBindWaitMs)) { + setlog("BindEx: previous WGC cleanup is still pending on Windows 10"); + return 0; + } + + if (win10Worker_.joinable()) { + win10Worker_.join(); + } + + win10WorkerStop_.store(false); + win10WorkerRunning_.store(true); + win10WorkerInitReady_.store(false); + win10WorkerInitSucceeded_.store(false); + win10WorkerCleaned_.store(false); + captureStopping_.store(false); + + auto self = std::static_pointer_cast(shared_from_this()); + try { + win10Worker_ = std::thread([self, hwnd]() { self->runWin10Worker(hwnd); }); + } catch (...) { + win10WorkerRunning_.store(false); + win10WorkerInitReady_.store(true); + win10WorkerInitSucceeded_.store(false); + return 0; + } + return 1; +} + +long WgcCapture::UnBindExInternal() { + captureStopping_.store(true); closeCaptureSession(); revokeItemClosed(); @@ -146,6 +248,51 @@ long WgcCapture::UnBindEx() { sharedHeight_ = 0; captureWidth_ = 0; captureHeight_ = 0; + framePoolWidth_ = 0; + framePoolHeight_ = 0; + frameSerial_ = 0; + hasWindowState_ = false; + lastWindowIconic_ = false; + pendingMetricsChanged_ = false; + pendingBecameIconic_ = false; + pendingRestored_ = false; + lastClientWidth_ = 0; + lastClientHeight_ = 0; + + return 0; +} + +long WgcCapture::UnBindExOnWindows10() { + if (!win10WorkerRunning_.load() && win10WorkerCleaned_.load() && !win10Worker_.joinable()) { + return 0; + } + + captureStopping_.store(true); + win10WorkerStop_.store(true); + + const unsigned long long deadline = ::GetTickCount64() + kWin10CloseWaitMs; + while (win10WorkerRunning_.load() && ::GetTickCount64() < deadline) { + ::Sleep(10); + } + + if (win10Worker_.joinable()) { + if (win10WorkerRunning_.load()) { + debugWgcCloseMessage("WGC worker stop timed out on Windows 10; cleanup continues in background"); + win10Worker_.detach(); + return 0; + } else { + win10Worker_.join(); + } + } + + itemClosed_.store(false); + hasFrame_ = false; + sharedWidth_ = 0; + sharedHeight_ = 0; + captureWidth_ = 0; + captureHeight_ = 0; + framePoolWidth_ = 0; + framePoolHeight_ = 0; frameSerial_ = 0; hasWindowState_ = false; lastWindowIconic_ = false; @@ -158,7 +305,99 @@ long WgcCapture::UnBindEx() { return 0; } -bool WgcCapture::Init(HWND _hwnd) { +void WgcCapture::runWin10Worker(HWND hwnd) { + g_win10WgcCleanupInFlight.fetch_add(1); + (void)runInMtaApartment( + [&]() -> long { + const bool ok = Init(hwnd, false); + win10WorkerInitSucceeded_.store(ok); + win10WorkerInitReady_.store(true); + if (ok) { + while (!win10WorkerStop_.load() && !itemClosed_.load()) { + updateLatestFrame(); + ::Sleep(8); + } + } + captureStopping_.store(true); + closeWin10WorkerObjects(); + win10WorkerCleaned_.store(true); + return 0; + }, + 0L); + win10WorkerRunning_.store(false); + g_win10WgcCleanupInFlight.fetch_sub(1); +} + +bool WgcCapture::waitForWin10WorkerInit(unsigned long timeout_ms) { + const unsigned long long deadline = ::GetTickCount64() + timeout_ms; + while (!win10WorkerInitReady_.load()) { + if (::GetTickCount64() >= deadline) { + return false; + } + ::Sleep(10); + } + return win10WorkerInitSucceeded_.load(); +} + +void WgcCapture::closeWin10WorkerObjects() { + if (item_ && hasClosedToken_) { + try { + item_.Closed(closedToken_); + } catch (...) { + } + } + closedToken_ = {}; + hasClosedToken_ = false; + + if (framePool_ && hasFrameArrivedToken_) { + try { + framePool_.FrameArrived(frameArrivedToken_); + } catch (...) { + } + } + frameArrivedToken_ = {}; + hasFrameArrivedToken_ = false; + + if (framePool_) { + try { + framePool_.Close(); + } catch (winrt::hresult_error &err) { + debugWgcCloseHresult("Direct3D11CaptureFramePool::Close", err); + } catch (...) { + debugWgcCloseMessage("Direct3D11CaptureFramePool::Close failed"); + } + } + + if (session_) { + try { + session_.Close(); + } catch (winrt::hresult_error &err) { + debugWgcCloseHresult("GraphicsCaptureSession::Close", err); + } catch (...) { + debugWgcCloseMessage("GraphicsCaptureSession::Close failed"); + } + } + + if (device_) { + try { + device_.Close(); + } catch (winrt::hresult_error &err) { + debugWgcCloseHresult("IDirect3DDevice::Close", err); + } catch (...) { + debugWgcCloseMessage("IDirect3DDevice::Close failed"); + } + } + + session_ = nullptr; + framePool_ = nullptr; + device_ = nullptr; + item_ = nullptr; + stagingTexture_.Release(); + d3dDeviceContext_.Release(); + d3dDevice_.Release(); +} + +bool WgcCapture::Init(HWND _hwnd, bool use_frame_arrived_event) { auto activation_factory = winrt::get_activation_factory(); auto interop_factory = activation_factory.as(); winrt::Windows::Graphics::Capture::GraphicsCaptureItem item = {nullptr}; @@ -235,6 +474,8 @@ bool WgcCapture::Init(HWND _hwnd) { const auto item_size = item.Size(); captureWidth_ = item_size.Width; captureHeight_ = item_size.Height; + framePoolWidth_ = item_size.Width; + framePoolHeight_ = item_size.Height; if (captureWidth_ <= 0 || captureHeight_ <= 0) { setlog("Invalid WGC item size width=%d height=%d", captureWidth_, captureHeight_); return false; @@ -257,18 +498,27 @@ bool WgcCapture::Init(HWND _hwnd) { item_ = item; itemClosed_.store(false); + captureStopping_.store(false); closedToken_ = item_.Closed([this](auto const &, auto const &) { itemClosed_.store(true); }); hasClosedToken_ = true; device_ = device; framePool_ = frame_pool; session_ = session; - frameArrivedToken_ = framePool_.FrameArrived([this](const Direct3D11CaptureFramePool &sender, auto const &) { - auto frame = tryGetLatestFrame(sender); - if (frame) { - copyFrameToStaging(frame); - } - }); - hasFrameArrivedToken_ = true; + if (use_frame_arrived_event) { + frameArrivedToken_ = framePool_.FrameArrived([this](const Direct3D11CaptureFramePool &sender, auto const &) { + if (captureStopping_.load()) { + return; + } + auto frame = tryGetLatestFrame(sender); + if (frame && !captureStopping_.load()) { + copyFrameToStaging(frame); + } + }); + hasFrameArrivedToken_ = true; + } else { + frameArrivedToken_ = {}; + hasFrameArrivedToken_ = false; + } try { session_.StartCapture(); @@ -284,11 +534,20 @@ bool WgcCapture::Init(HWND _hwnd) { } bool WgcCapture::requestCapture(int x1, int y1, int w, int h, Image &img) { + const bool win10_worker = shouldUseWgcWorkerThread(); + if (win10_worker && !waitForWin10WorkerInit(kWin10RequestInitWaitMs)) { + setlog("requestCapture: WGC worker init is not ready"); + return false; + } if (itemClosed_.load()) { setlog("requestCapture: capture item closed (target window gone)"); return false; } if (isDeviceLost()) { + if (win10_worker) { + setlog("requestCapture: D3D device lost on Windows 10"); + return false; + } setlog("requestCapture: D3D device lost, rebuilding WGC capture"); recoverFromDeviceLoss(); return false; @@ -324,6 +583,11 @@ bool WgcCapture::requestCapture(int x1, int y1, int w, int h, Image &img) { return false; } + if (!hasCapturedFrame()) { + const unsigned long long initial_serial = currentFrameSerial(); + waitForFramesAfter(initial_serial, 1, win10_worker ? kWin10InitialFrameWaitMs : 500, !win10_worker); + } + img.create(w, h); if (!_shmem || !_pmutex) { setlog("requestCapture: shared memory not initialized"); @@ -331,16 +595,38 @@ bool WgcCapture::requestCapture(int x1, int y1, int w, int h, Image &img) { } if (metrics_changed || restored) { - // 尺寸变化后队列里可能还有旧帧,先抽干一次,再按状态变化等待后续帧。 - updateLatestFrame(); - const unsigned long long drained_serial = currentFrameSerial(); - // 恢复后只等 1 帧、且只给很短的自恢复窗口(150ms):WGC 常在恢复后停吐帧, - // 与其干等再重启,不如尽快落到下面的重启会话路径(能自恢复的机器仍会在 150ms 内命中)。 - const unsigned int frame_count = 1; - const unsigned long timeout_ms = restored ? 150 : 100; - if (!waitForFramesAfter(drained_serial, frame_count, timeout_ms)) { + // 尺寸变化后队列里可能已有新尺寸帧。先尝试抽取;只有新帧已经匹配当前客户区 + // 尺寸才直接使用。Windows 10 最大化时可能先吐旧尺寸/过渡帧,过早放行会导致 + // 第一次截图仍是旧画面,第二次才更新。 + const long target_width = _width; + const long target_height = _height; + auto staging_matches_target = [&]() { + std::scoped_lock lock(frameMutex_); + if (!hasFrame_ || !stagingTexture_) { + return false; + } + D3D11_TEXTURE2D_DESC staging_desc = {}; + stagingTexture_->GetDesc(&staging_desc); + return static_cast(staging_desc.Width) == target_width && + static_cast(staging_desc.Height) == target_height; + }; + const unsigned long long before_update_serial = currentFrameSerial(); + const bool already_has_metric_frame = staging_matches_target(); + if (!win10_worker) { + updateLatestFrame(); + } + const bool got_metric_frame = + staging_matches_target() && (already_has_metric_frame || currentFrameSerial() > before_update_serial); + // 如果抽取时没拿到匹配新尺寸的帧,再短等 1 帧。 + const unsigned long timeout_ms = win10_worker ? kWin10MetricsFrameWaitMs : (restored ? 150 : 100); + if (!got_metric_frame && !waitForFramesAfter(before_update_serial, 1, timeout_ms, !win10_worker)) { + if (win10_worker) { + // Windows 10 上同步 Close/Create/StartCapture 可能拖住调用方 GUI 线程。 + // resize 后 WGC 通常会自行继续吐新帧;本次不重启,避免截图 API 变成长阻塞点。 + setlog("requestCapture: no fresh WGC frame after metrics change on Windows 10"); + return false; + } // 尺寸变化/最大化/恢复后 WGC 偶尔不继续吐帧,重启捕获会话能避免拿旧 staging 截新坐标。 - setlog("requestCapture: no fresh WGC frame after window metrics change, restarting capture session"); if (!restartCaptureSession()) { setlog("requestCapture: restart WGC capture session failed after metrics change"); return false; @@ -351,8 +637,21 @@ bool WgcCapture::requestCapture(int x1, int y1, int w, int h, Image &img) { return false; } } - } else if (!updateLatestFrame()) { - return false; + if (win10_worker && !staging_matches_target()) { + setlog("requestCapture: WGC frame size not updated after metrics change on Windows 10"); + return false; + } + } else { + const unsigned long long before_update_serial = currentFrameSerial(); + if (!win10_worker) { + if (!updateLatestFrame()) { + return false; + } + } + if (currentFrameSerial() == before_update_serial) { + const unsigned long fresh_timeout_ms = win10_worker ? kWin10FreshFrameWaitMs : 50; + (void)waitForFramesAfter(before_update_serial, 1, fresh_timeout_ms, !win10_worker); + } } if (!hasCapturedFrame()) { @@ -374,7 +673,12 @@ bool WgcCapture::requestCapture(int x1, int y1, int w, int h, Image &img) { } uint8_t *pData = (uint8_t *)mappedResource.pData; - if (_pmutex && _shmem) { + { + std::scoped_lock shared_lock(sharedResourceMutex_); + if (!_pmutex || !_shmem) { + setlog("requestCapture: shared memory not initialized"); + return false; + } _pmutex->lock(); fmtFrameInfo(_shmem->data(), _hwnd, _width, _height); _pmutex->unlock(); @@ -427,6 +731,13 @@ void WgcCapture::refreshMetrics() { } void WgcCapture::waitForBindReady() { + if (shouldUseWgcWorkerThread()) { + // Windows 10 WGC can stall while Create/StartCapture waits on the system broker. + // BindWindow is commonly called on the GUI thread, so keep it non-blocking and let + // requestCapture perform the bounded first-frame wait. + return; + } + // WGC 绑定后由后台异步推送首帧;等第一帧到达再返回, // 避免 bind 后第一次 FindColor/FindPic/OCR 因首帧未就绪而假失败(与 HookCapture 行为一致)。 if (!framePool_) { @@ -460,6 +771,7 @@ bool WgcCapture::ensureStagingTexture(int width, int height) { } bool WgcCapture::ensureSharedResources(int width, int height) { + std::scoped_lock shared_lock(sharedResourceMutex_); if (_shmem && _pmutex && sharedWidth_ == width && sharedHeight_ == height) { return true; } @@ -512,8 +824,8 @@ bool WgcCapture::refreshWindowMetrics(bool *iconic_changed, bool *is_iconic) { _width = client_rect.right - client_rect.left; _height = client_rect.bottom - client_rect.top; - const bool changed = !hasWindowState_ || lastClientWidth_ != _width || lastClientHeight_ != _height || - was_iconic != now_iconic; + const bool changed = + !hasWindowState_ || lastClientWidth_ != _width || lastClientHeight_ != _height || was_iconic != now_iconic; set_out(iconic_changed, hasWindowState_ && was_iconic != now_iconic); set_out(is_iconic, now_iconic); hasWindowState_ = true; @@ -542,6 +854,28 @@ bool WgcCapture::getClientBox(int surface_width, int surface_height, D3D11_BOX & const int expected_w = client_rect.right - client_rect.left; const int expected_h = client_rect.bottom - client_rect.top; + constexpr int kClientSurfaceTolerance = 8; + auto make_full_surface_candidate = [&]() { + ClientBoxCandidate candidate; + const int extra_w = surface_width - expected_w; + const int extra_h = surface_height - expected_h; + if (extra_w < 0 || extra_h < 0 || extra_w > kClientSurfaceTolerance || extra_h > kClientSurfaceTolerance) { + return candidate; + } + + candidate.box.left = 0; + candidate.box.top = 0; + candidate.box.right = static_cast(expected_w); + candidate.box.bottom = static_cast(expected_h); + candidate.box.front = 0; + candidate.box.back = 1; + candidate.width = expected_w; + candidate.height = expected_h; + candidate.clipped_pixels = 0; + candidate.frame_mismatch = extra_w + extra_h; + candidate.valid = true; + return candidate; + }; auto make_candidate = [&](const RECT &base_rect) { ClientBoxCandidate candidate; const int raw_left = client_origin.x - base_rect.left; @@ -575,6 +909,7 @@ bool WgcCapture::getClientBox(int surface_width, int surface_height, D3D11_BOX & }; const ClientBoxCandidate candidates[] = { + make_full_surface_candidate(), make_candidate(visible_rect), make_candidate(window_rect), }; @@ -595,6 +930,9 @@ bool WgcCapture::getClientBox(int surface_width, int surface_height, D3D11_BOX & if (!best.valid) { return false; } + if (best.clipped_pixels > 0) { + return false; + } // 最大化窗口的 DWM 扩展边界可能和 WGC surface 原点差几个像素; // 同时尝试 DWM 外框和 Win32 窗口框,优先选裁剪少且外框尺寸更贴近 surface 的客户区 box。 @@ -646,6 +984,10 @@ void WgcCapture::closeCaptureSession() { } bool WgcCapture::restartCaptureSession() { + if (shouldUseWgcWorkerThread()) { + setlog("restartCaptureSession: skipped synchronous WGC restart on Windows 10"); + return false; + } if (!device_ || !item_) { return false; } @@ -666,6 +1008,8 @@ bool WgcCapture::restartCaptureSession() { captureWidth_ = item_size.Width; captureHeight_ = item_size.Height; + framePoolWidth_ = item_size.Width; + framePoolHeight_ = item_size.Height; if (!ensureStagingTexture(_width, _height)) { setlog("restartCaptureSession: create staging texture failed"); return false; @@ -776,28 +1120,32 @@ bool WgcCapture::copyFrameToStaging(const Direct3D11CaptureFrame &frame) { return hasCapturedFrame(); } + const auto frame_content_size = frame.ContentSize(); + const bool content_size_changed = + frame_content_size.Width > 0 && frame_content_size.Height > 0 && + (frame_content_size.Width != framePoolWidth_ || frame_content_size.Height != framePoolHeight_); + if (surface_w != captureWidth_ || surface_h != captureHeight_) { + captureWidth_ = surface_w; + captureHeight_ = surface_h; + } + if (content_size_changed && framePool_) { + try { + framePool_.Recreate(device_, + static_cast(surface_desc.Format), + 2, frame_content_size); + framePoolWidth_ = frame_content_size.Width; + framePoolHeight_ = frame_content_size.Height; + } catch (...) { + // 帧池重建失败不丢弃当前帧;下一帧继续尝试。 + } + } + D3D11_BOX client_box = {}; int client_w = 0; int client_h = 0; if (!getClientBox(surface_w, surface_h, client_box, client_w, client_h)) { return hasCapturedFrame(); } - - if (surface_w != captureWidth_ || surface_h != captureHeight_) { - captureWidth_ = surface_w; - captureHeight_ = surface_h; - // 窗口尺寸增大时让 WGC 帧池跟随内容大小重建(帧池按 content size 约定)。 - const auto frame_content_size = frame.ContentSize(); - if (frame_content_size.Width > 0 && frame_content_size.Height > 0) { - try { - framePool_.Recreate(device_, - winrt::Windows::Graphics::DirectX::DirectXPixelFormat::B8G8R8A8UIntNormalized, 2, - frame_content_size); - } catch (...) { - // 重建失败下一帧再试,不影响本帧拷贝。 - } - } - } if (!ensureStagingTexture(client_w, client_h)) { setlog("copyFrameToStaging: resize client staging texture failed"); return hasCapturedFrame(); @@ -845,6 +1193,9 @@ Direct3D11CaptureFrame WgcCapture::tryGetLatestFrame(const Direct3D11CaptureFram } bool WgcCapture::updateLatestFrame() { + if (!framePool_) { + return hasCapturedFrame(); + } Direct3D11CaptureFrame frame = tryGetLatestFrame(framePool_); if (frame) { return copyFrameToStaging(frame); @@ -853,11 +1204,11 @@ bool WgcCapture::updateLatestFrame() { return hasCapturedFrame(); } -bool WgcCapture::waitForFramesAfter(unsigned long long frame_serial, unsigned int frame_count, - unsigned long timeout_ms) { +bool WgcCapture::waitForFramesAfter(unsigned long long frame_serial, unsigned int frame_count, unsigned long timeout_ms, + bool poll_latest) { const unsigned long long deadline = ::GetTickCount64() + timeout_ms; do { - if (updateLatestFrame() && currentFrameSerial() >= frame_serial + frame_count) { + if (poll_latest && updateLatestFrame() && currentFrameSerial() >= frame_serial + frame_count) { return true; } if (currentFrameSerial() >= frame_serial + frame_count) { @@ -866,7 +1217,9 @@ bool WgcCapture::waitForFramesAfter(unsigned long long frame_serial, unsigned in ::Sleep(8); } while (::GetTickCount64() < deadline); - updateLatestFrame(); + if (poll_latest) { + updateLatestFrame(); + } return currentFrameSerial() >= frame_serial + frame_count; } diff --git a/libop/capture/backends/WgcCapture.h b/libop/capture/backends/WgcCapture.h index a011413..eccce75 100644 --- a/libop/capture/backends/WgcCapture.h +++ b/libop/capture/backends/WgcCapture.h @@ -12,6 +12,7 @@ #include #include #include +#include #pragma comment(lib, "d3d11.lib") #ifdef OP_ENABLE_WGC // this code ref https://www.jianshu.com/p/e775b0f45376 @@ -33,7 +34,7 @@ class WgcCapture : public ICaptureBackend { void refreshMetrics() override; void waitForBindReady() override; - bool Init(HWND _hwnd); + bool Init(HWND _hwnd, bool use_frame_arrived_event = true); private: ATL::CComPtr d3dDevice_; @@ -51,6 +52,8 @@ class WgcCapture : public ICaptureBackend { FrameInfo m_frameInfo{}; long captureWidth_{0}; long captureHeight_{0}; + long framePoolWidth_{0}; + long framePoolHeight_{0}; bool hasFrame_{false}; int sharedWidth_{0}; int sharedHeight_{0}; @@ -62,19 +65,35 @@ class WgcCapture : public ICaptureBackend { bool pendingRestored_{false}; long lastClientWidth_{0}; long lastClientHeight_{0}; + std::atomic captureStopping_{false}; + std::thread win10Worker_; + std::atomic win10WorkerStop_{false}; + std::atomic win10WorkerRunning_{false}; + std::atomic win10WorkerInitReady_{false}; + std::atomic win10WorkerInitSucceeded_{false}; + std::atomic win10WorkerCleaned_{true}; std::mutex frameMutex_; + std::mutex sharedResourceMutex_; bool ensureStagingTexture(int width, int height); bool ensureSharedResources(int width, int height); bool refreshWindowMetrics(bool *iconic_changed = nullptr, bool *is_iconic = nullptr); bool getClientBox(int surface_width, int surface_height, D3D11_BOX &client_box, int &client_width, int &client_height); + bool DeferBindReleaseAfterUnBind() const override; + long BindExOnWindows10(HWND hwnd); + long UnBindExInternal(); + long UnBindExOnWindows10(); + void runWin10Worker(HWND hwnd); + bool waitForWin10WorkerInit(unsigned long timeout_ms); + void closeWin10WorkerObjects(); void closeCaptureSession(); bool restartCaptureSession(); bool copyFrameToStaging(const Direct3D11CaptureFrame &frame); Direct3D11CaptureFrame tryGetLatestFrame(const Direct3D11CaptureFramePool &frame_pool); bool updateLatestFrame(); - bool waitForFramesAfter(unsigned long long frame_serial, unsigned int frame_count, unsigned long timeout_ms); + bool waitForFramesAfter(unsigned long long frame_serial, unsigned int frame_count, unsigned long timeout_ms, + bool poll_latest = true); unsigned long long currentFrameSerial(); bool hasCapturedFrame(); void fmtFrameInfo(void *dst, HWND hwnd, int w, int h, bool inc = true); diff --git a/libop/client/ClientInput.cpp b/libop/client/ClientInput.cpp index 108c8d7..d7890a5 100644 --- a/libop/client/ClientInput.cpp +++ b/libop/client/ClientInput.cpp @@ -173,6 +173,10 @@ void op::Client::MiddleClick(long *ret) { internal::set_result(ret, m_context->bkproc._mouse->MiddleClick()); } +void op::Client::MiddleDoubleClick(long *ret) { + internal::set_result(ret, m_context->bkproc._mouse->MiddleDoubleClick()); +} + void op::Client::MiddleDown(long *ret) { internal::set_result(ret, m_context->bkproc._mouse->MiddleDown()); } @@ -185,6 +189,10 @@ void op::Client::RightClick(long *ret) { internal::set_result(ret, m_context->bkproc._mouse->RightClick()); } +void op::Client::RightDoubleClick(long *ret) { + internal::set_result(ret, m_context->bkproc._mouse->RightDoubleClick()); +} + void op::Client::RightDown(long *ret) { internal::set_result(ret, m_context->bkproc._mouse->RightDown()); } @@ -193,6 +201,46 @@ void op::Client::RightUp(long *ret) { internal::set_result(ret, m_context->bkproc._mouse->RightUp()); } +void op::Client::XButton1Click(long *ret) { + internal::set_result(ret, m_context->bkproc._mouse->XButton1Click()); +} + +void op::Client::XButton1DoubleClick(long *ret) { + internal::set_result(ret, m_context->bkproc._mouse->XButton1DoubleClick()); +} + +void op::Client::XButton1Down(long *ret) { + internal::set_result(ret, m_context->bkproc._mouse->XButton1Down()); +} + +void op::Client::XButton1Up(long *ret) { + internal::set_result(ret, m_context->bkproc._mouse->XButton1Up()); +} + +void op::Client::XButton2Click(long *ret) { + internal::set_result(ret, m_context->bkproc._mouse->XButton2Click()); +} + +void op::Client::XButton2DoubleClick(long *ret) { + internal::set_result(ret, m_context->bkproc._mouse->XButton2DoubleClick()); +} + +void op::Client::XButton2Down(long *ret) { + internal::set_result(ret, m_context->bkproc._mouse->XButton2Down()); +} + +void op::Client::XButton2Up(long *ret) { + internal::set_result(ret, m_context->bkproc._mouse->XButton2Up()); +} + +void op::Client::Wheel(long delta, long *ret) { + internal::set_result(ret, m_context->bkproc._mouse->Wheel(delta)); +} + +void op::Client::HWheel(long delta, long *ret) { + internal::set_result(ret, m_context->bkproc._mouse->HWheel(delta)); +} + void op::Client::WheelDown(long *ret) { internal::set_result(ret, m_context->bkproc._mouse->WheelDown()); } @@ -281,11 +329,7 @@ void op::Client::KeyPressStr(const wchar_t *key_str, long delay, long *ret) { return; auto nlen = wcslen(key_str); for (size_t i = 0; i < nlen; ++i) { - key_combo_t combo; - if (!resolve_char_key_combo(key_str[i], combo)) - return; - - const long key_ret = key_combo_press(m_context->bkproc._keyboard.get(), combo); + const long key_ret = m_context->bkproc._keyboard->InputChar(key_str[i]); internal::set_result(ret, key_ret); if (key_ret == 0) return; diff --git a/libop/com/OpAutomation.cpp b/libop/com/OpAutomation.cpp index 6b12b5b..ae5ef59 100644 --- a/libop/com/OpAutomation.cpp +++ b/libop/com/OpAutomation.cpp @@ -551,6 +551,12 @@ STDMETHODIMP OpAutomation::MiddleClick(LONG *ret) { return S_OK; } +STDMETHODIMP OpAutomation::MiddleDoubleClick(LONG *ret) { + obj.MiddleDoubleClick(ret); + + return S_OK; +} + STDMETHODIMP OpAutomation::MiddleDown(LONG *ret) { obj.MiddleDown(ret); @@ -569,6 +575,12 @@ STDMETHODIMP OpAutomation::RightClick(LONG *ret) { return S_OK; } +STDMETHODIMP OpAutomation::RightDoubleClick(LONG *ret) { + obj.RightDoubleClick(ret); + + return S_OK; +} + STDMETHODIMP OpAutomation::RightDown(LONG *ret) { obj.RightDown(ret); @@ -581,6 +593,54 @@ STDMETHODIMP OpAutomation::RightUp(LONG *ret) { return S_OK; } +STDMETHODIMP OpAutomation::XButton1Click(LONG *ret) { + obj.XButton1Click(ret); + + return S_OK; +} + +STDMETHODIMP OpAutomation::XButton1DoubleClick(LONG *ret) { + obj.XButton1DoubleClick(ret); + + return S_OK; +} + +STDMETHODIMP OpAutomation::XButton1Down(LONG *ret) { + obj.XButton1Down(ret); + + return S_OK; +} + +STDMETHODIMP OpAutomation::XButton1Up(LONG *ret) { + obj.XButton1Up(ret); + + return S_OK; +} + +STDMETHODIMP OpAutomation::XButton2Click(LONG *ret) { + obj.XButton2Click(ret); + + return S_OK; +} + +STDMETHODIMP OpAutomation::XButton2DoubleClick(LONG *ret) { + obj.XButton2DoubleClick(ret); + + return S_OK; +} + +STDMETHODIMP OpAutomation::XButton2Down(LONG *ret) { + obj.XButton2Down(ret); + + return S_OK; +} + +STDMETHODIMP OpAutomation::XButton2Up(LONG *ret) { + obj.XButton2Up(ret); + + return S_OK; +} + STDMETHODIMP OpAutomation::WheelDown(LONG *ret) { obj.WheelDown(ret); @@ -592,6 +652,16 @@ STDMETHODIMP OpAutomation::WheelUp(LONG *ret) { return S_OK; } +STDMETHODIMP OpAutomation::Wheel(LONG delta, LONG *ret) { + obj.Wheel(delta, ret); + return S_OK; +} + +STDMETHODIMP OpAutomation::HWheel(LONG delta, LONG *ret) { + obj.HWheel(delta, ret); + return S_OK; +} + STDMETHODIMP OpAutomation::SetMouseDelay(BSTR type, LONG delay, LONG *ret) { obj.SetMouseDelay(type, delay, ret); return S_OK; diff --git a/libop/com/OpAutomation.h b/libop/com/OpAutomation.h index 358d646..e2173ff 100644 --- a/libop/com/OpAutomation.h +++ b/libop/com/OpAutomation.h @@ -205,20 +205,34 @@ class ATL_NO_VTABLE OpAutomation STDMETHOD(LeftUp)(LONG *ret); // 按下鼠标中键 STDMETHOD(MiddleClick)(LONG *ret); + // 双击鼠标中键 + STDMETHOD(MiddleDoubleClick)(LONG *ret); // 按住鼠标中键 STDMETHOD(MiddleDown)(LONG *ret); // 弹起鼠标中键 STDMETHOD(MiddleUp)(LONG *ret); // 按下鼠标右键 STDMETHOD(RightClick)(LONG *ret); + // 双击鼠标右键 + STDMETHOD(RightDoubleClick)(LONG *ret); // 按住鼠标右键 STDMETHOD(RightDown)(LONG *ret); // 弹起鼠标右键 STDMETHOD(RightUp)(LONG *ret); + STDMETHOD(XButton1Click)(LONG *ret); + STDMETHOD(XButton1DoubleClick)(LONG *ret); + STDMETHOD(XButton1Down)(LONG *ret); + STDMETHOD(XButton1Up)(LONG *ret); + STDMETHOD(XButton2Click)(LONG *ret); + STDMETHOD(XButton2DoubleClick)(LONG *ret); + STDMETHOD(XButton2Down)(LONG *ret); + STDMETHOD(XButton2Up)(LONG *ret); // 滚轮向下滚 STDMETHOD(WheelDown)(LONG *ret); // 滚轮向上滚 STDMETHOD(WheelUp)(LONG *ret); + STDMETHOD(Wheel)(LONG delta, LONG *ret); + STDMETHOD(HWheel)(LONG delta, LONG *ret); // 设置鼠标单击或者双击时,鼠标按下和弹起的时间间隔 STDMETHOD(SetMouseDelay)(BSTR type, LONG delay, LONG *ret); // 获取指定的按键状态.(前台信息,不是后台) diff --git a/libop/com/op.idl b/libop/com/op.idl index e019783..d4b4710 100644 --- a/libop/com/op.idl +++ b/libop/com/op.idl @@ -104,15 +104,28 @@ interface IOpAutomation : IDispatch [id(127)] HRESULT LeftUp([out, retval] LONG* ret); [id(128)] HRESULT MiddleClick([out, retval] LONG* ret); + [id(336)] HRESULT MiddleDoubleClick([out, retval] LONG* ret); [id(129)] HRESULT MiddleDown([out, retval] LONG* ret); [id(130)] HRESULT MiddleUp([out, retval] LONG* ret); [id(131)] HRESULT RightClick([out, retval] LONG* ret); + [id(337)] HRESULT RightDoubleClick([out, retval] LONG* ret); [id(132)] HRESULT RightDown([out, retval] LONG* ret); [id(133)] HRESULT RightUp([out, retval] LONG* ret); + [id(338)] HRESULT XButton1Click([out, retval] LONG* ret); + [id(339)] HRESULT XButton1DoubleClick([out, retval] LONG* ret); + [id(340)] HRESULT XButton1Down([out, retval] LONG* ret); + [id(341)] HRESULT XButton1Up([out, retval] LONG* ret); + [id(342)] HRESULT XButton2Click([out, retval] LONG* ret); + [id(343)] HRESULT XButton2DoubleClick([out, retval] LONG* ret); + [id(344)] HRESULT XButton2Down([out, retval] LONG* ret); + [id(345)] HRESULT XButton2Up([out, retval] LONG* ret); + [id(134)] HRESULT WheelDown([out, retval] LONG* ret); [id(135)] HRESULT WheelUp([out, retval] LONG* ret); + [id(346)] HRESULT Wheel([in] LONG delta, [out, retval] LONG* ret); + [id(347)] HRESULT HWheel([in] LONG delta, [out, retval] LONG* ret); [id(136)] HRESULT GetKeyState([in] LONG vk_code, [out, retval] LONG* ret); diff --git a/libop/com/op.rc b/libop/com/op.rc index 8e05def..ca39802 100644 Binary files a/libop/com/op.rc and b/libop/com/op.rc differ diff --git a/libop/hook/HookModule.h b/libop/hook/HookModule.h new file mode 100644 index 0000000..2607748 --- /dev/null +++ b/libop/hook/HookModule.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../runtime/AutomationModes.h" +#include "../runtime/RuntimeEnvironment.h" + +#include + +namespace op::hook { + +inline std::wstring ResolveHookModuleName(bool target_is64) { + const std::wstring current = RuntimeEnvironment::getOpName(); + if (target_is64 == (OP64 != 0) && !current.empty()) { + return current; + } + + if (current.find(L"op_c_api") != std::wstring::npos) { + return target_is64 ? L"op_c_api_x64.dll" : L"op_c_api_x86.dll"; + } + return target_is64 ? L"op_x64.dll" : L"op_x86.dll"; +} + +} // namespace op::hook diff --git a/libop/hook/HookProtocol.h b/libop/hook/HookProtocol.h index 43673ce..ffc23e7 100644 --- a/libop/hook/HookProtocol.h +++ b/libop/hook/HookProtocol.h @@ -10,6 +10,14 @@ #define OP_WM_RBUTTONDOWN (WM_USER + WM_RBUTTONDOWN) #define OP_WM_RBUTTONUP (WM_USER + WM_RBUTTONUP) #define OP_WM_MOUSEWHEEL (WM_USER + WM_MOUSEWHEEL) +#define OP_WM_MOUSEHWHEEL (WM_USER + WM_MOUSEHWHEEL) +#define OP_WM_XBUTTONDOWN (WM_USER + WM_XBUTTONDOWN) +#define OP_WM_XBUTTONUP (WM_USER + WM_XBUTTONUP) +#define OP_WM_LBUTTONDBLCLK (WM_USER + WM_LBUTTONDBLCLK) +#define OP_WM_MBUTTONDBLCLK (WM_USER + WM_MBUTTONDBLCLK) +#define OP_WM_RBUTTONDBLCLK (WM_USER + WM_RBUTTONDBLCLK) +#define OP_WM_XBUTTONDBLCLK (WM_USER + WM_XBUTTONDBLCLK) #define OP_WM_KEYDOWN (WM_USER + WM_KEYDOWN) #define OP_WM_KEYUP (WM_USER + WM_KEYUP) +#define OP_WM_CHAR (WM_USER + WM_CHAR) #endif // OP_HOOK_HOOK_PROTOCOL_H_ diff --git a/libop/hook/InputHook.cpp b/libop/hook/InputHook.cpp index 43c248d..3c3a076 100644 --- a/libop/hook/InputHook.cpp +++ b/libop/hook/InputHook.cpp @@ -420,6 +420,8 @@ void fill_mouse_state(DWORD size, LPVOID ptr) { state.rgbButtons[0] = InputHook::m_mouseState.abButtons[0]; state.rgbButtons[1] = InputHook::m_mouseState.abButtons[1]; state.rgbButtons[2] = InputHook::m_mouseState.abButtons[2]; + state.rgbButtons[3] = InputHook::m_mouseState.abButtons[3]; + state.rgbButtons[4] = InputHook::m_mouseState.abButtons[4]; memcpy(ptr, &state, sizeof(state)); } } @@ -853,26 +855,29 @@ void InputHook::moveTo(LPARAM lp) { void InputHook::button(LPARAM lp, int key, bool down) { moveTo(lp); - if (0 <= key && key < 3) { + if (0 <= key && key < 5) { m_mouseState.abButtons[key] = down ? 0x80 : 0; + static constexpr int vk_buttons[] = {VK_LBUTTON, VK_MBUTTON, VK_RBUTTON, VK_XBUTTON1, VK_XBUTTON2}; + m_vkState[vk_buttons[key]] = down ? 0x80 : 0; std::lock_guard lock(g_eventMutex); push_dinput_event(g_mouseEvents, mouse_button_offset(key), down ? 0x80 : 0); - static constexpr USHORT down_flags[] = {RI_MOUSE_LEFT_BUTTON_DOWN, RI_MOUSE_RIGHT_BUTTON_DOWN, - RI_MOUSE_MIDDLE_BUTTON_DOWN}; - static constexpr USHORT up_flags[] = {RI_MOUSE_LEFT_BUTTON_UP, RI_MOUSE_RIGHT_BUTTON_UP, - RI_MOUSE_MIDDLE_BUTTON_UP}; + static constexpr USHORT down_flags[] = {RI_MOUSE_LEFT_BUTTON_DOWN, RI_MOUSE_MIDDLE_BUTTON_DOWN, + RI_MOUSE_RIGHT_BUTTON_DOWN, RI_MOUSE_BUTTON_4_DOWN, + RI_MOUSE_BUTTON_5_DOWN}; + static constexpr USHORT up_flags[] = {RI_MOUSE_LEFT_BUTTON_UP, RI_MOUSE_MIDDLE_BUTTON_UP, + RI_MOUSE_RIGHT_BUTTON_UP, RI_MOUSE_BUTTON_4_UP, RI_MOUSE_BUTTON_5_UP}; push_raw_event(make_raw_mouse(0, 0, down ? down_flags[key] : up_flags[key], 0)); } } -void InputHook::updateWheel(WPARAM wp, LPARAM lp) { +void InputHook::updateWheel(WPARAM wp, LPARAM lp, bool horizontal) { moveTo(lp); const SHORT delta = static_cast(HIWORD(wp)); m_wheelDelta += delta; std::lock_guard lock(g_eventMutex); push_dinput_event(g_mouseEvents, DIMOFS_Z, static_cast(static_cast(delta))); - push_raw_event(make_raw_mouse(0, 0, RI_MOUSE_WHEEL, static_cast(delta))); + push_raw_event(make_raw_mouse(0, 0, horizontal ? RI_MOUSE_HWHEEL : RI_MOUSE_WHEEL, static_cast(delta))); } LONG InputHook::consumeWheelDelta() { @@ -1142,36 +1147,73 @@ LRESULT CALLBACK opWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam switch (message) { case OP_WM_MOUSEMOVE: InputHook::moveTo(lparam); - dispatch_window_message(hwnd, WM_MOUSEMOVE, 0, lparam); + dispatch_window_message(hwnd, WM_MOUSEMOVE, wparam, lparam); return 1; case OP_WM_LBUTTONDOWN: InputHook::button(lparam, 0, true); - dispatch_window_message(hwnd, WM_LBUTTONDOWN, MK_LBUTTON, lparam); + dispatch_window_message(hwnd, WM_LBUTTONDOWN, wparam ? wparam : MK_LBUTTON, lparam); + return 1; + case OP_WM_LBUTTONDBLCLK: + InputHook::button(lparam, 0, true); + dispatch_window_message(hwnd, WM_LBUTTONDBLCLK, wparam ? wparam : MK_LBUTTON, lparam); return 1; case OP_WM_LBUTTONUP: InputHook::button(lparam, 0, false); - dispatch_window_message(hwnd, WM_LBUTTONUP, 0, lparam); + dispatch_window_message(hwnd, WM_LBUTTONUP, wparam, lparam); return 1; case OP_WM_MBUTTONDOWN: InputHook::button(lparam, 1, true); - dispatch_window_message(hwnd, WM_MBUTTONDOWN, MK_MBUTTON, lparam); + dispatch_window_message(hwnd, WM_MBUTTONDOWN, wparam ? wparam : MK_MBUTTON, lparam); + return 1; + case OP_WM_MBUTTONDBLCLK: + InputHook::button(lparam, 1, true); + dispatch_window_message(hwnd, WM_MBUTTONDBLCLK, wparam ? wparam : MK_MBUTTON, lparam); return 1; case OP_WM_MBUTTONUP: InputHook::button(lparam, 1, false); - dispatch_window_message(hwnd, WM_MBUTTONUP, 0, lparam); + dispatch_window_message(hwnd, WM_MBUTTONUP, wparam, lparam); return 1; case OP_WM_RBUTTONDOWN: InputHook::button(lparam, 2, true); - dispatch_window_message(hwnd, WM_RBUTTONDOWN, MK_RBUTTON, lparam); + dispatch_window_message(hwnd, WM_RBUTTONDOWN, wparam ? wparam : MK_RBUTTON, lparam); + return 1; + case OP_WM_RBUTTONDBLCLK: + InputHook::button(lparam, 2, true); + dispatch_window_message(hwnd, WM_RBUTTONDBLCLK, wparam ? wparam : MK_RBUTTON, lparam); return 1; case OP_WM_RBUTTONUP: InputHook::button(lparam, 2, false); - dispatch_window_message(hwnd, WM_RBUTTONUP, 0, lparam); + dispatch_window_message(hwnd, WM_RBUTTONUP, wparam, lparam); + return 1; + case OP_WM_XBUTTONDOWN: { + const WORD xbutton = HIWORD(wparam); + const int key = xbutton == XBUTTON1 ? 3 : 4; + InputHook::button(lparam, key, true); + dispatch_window_message(hwnd, WM_XBUTTONDOWN, wparam, lparam); return 1; + } + case OP_WM_XBUTTONDBLCLK: { + const WORD xbutton = HIWORD(wparam); + const int key = xbutton == XBUTTON1 ? 3 : 4; + InputHook::button(lparam, key, true); + dispatch_window_message(hwnd, WM_XBUTTONDBLCLK, wparam, lparam); + return 1; + } + case OP_WM_XBUTTONUP: { + const WORD xbutton = HIWORD(wparam); + const int key = xbutton == XBUTTON1 ? 3 : 4; + InputHook::button(lparam, key, false); + dispatch_window_message(hwnd, WM_XBUTTONUP, wparam, lparam); + return 1; + } case OP_WM_MOUSEWHEEL: - InputHook::updateWheel(wparam, lparam); + InputHook::updateWheel(wparam, lparam, false); dispatch_window_message(hwnd, WM_MOUSEWHEEL, wparam, client_to_screen_lparam(hwnd, lparam)); return 1; + case OP_WM_MOUSEHWHEEL: + InputHook::updateWheel(wparam, lparam, true); + dispatch_window_message(hwnd, WM_MOUSEHWHEEL, wparam, client_to_screen_lparam(hwnd, lparam)); + return 1; case OP_WM_KEYDOWN: InputHook::updateKey(wparam, true); dispatch_window_message(hwnd, WM_KEYDOWN, wparam, lparam ? lparam : make_key_lparam(wparam, false)); @@ -1180,6 +1222,9 @@ LRESULT CALLBACK opWndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam InputHook::updateKey(wparam, false); dispatch_window_message(hwnd, WM_KEYUP, wparam, lparam ? lparam : make_key_lparam(wparam, true)); return 1; + case OP_WM_CHAR: + dispatch_window_message(hwnd, WM_CHAR, wparam, lparam ? lparam : 1); + return 1; } return ::CallWindowProc(g_rawWindowProc, hwnd, message, wparam, lparam); diff --git a/libop/hook/InputHook.h b/libop/hook/InputHook.h index 91b355b..5673f75 100644 --- a/libop/hook/InputHook.h +++ b/libop/hook/InputHook.h @@ -8,8 +8,8 @@ namespace op::hook { struct MouseState { LONG lAxisX; LONG lAxisY; - BYTE abButtons[3]; - BYTE bPadding; // 保持结构体 4 字节对齐。 + BYTE abButtons[5]; + BYTE bPadding[3]; // 保持结构体 4 字节对齐。 }; class InputHook { @@ -22,7 +22,7 @@ class InputHook { static int release(); static void moveTo(LPARAM lp); static void button(LPARAM lp, int key, bool down); - static void updateWheel(WPARAM, LPARAM); + static void updateWheel(WPARAM, LPARAM, bool horizontal); static LONG consumeWheelDelta(); static void updateKey(WPARAM vk, bool down); static bool isKeyDown(int vk); diff --git a/libop/hook/InputHookClient.cpp b/libop/hook/InputHookClient.cpp index 1440a2e..1e272f1 100644 --- a/libop/hook/InputHookClient.cpp +++ b/libop/hook/InputHookClient.cpp @@ -2,6 +2,7 @@ #include "../runtime/AutomationModes.h" #include "../runtime/RuntimeUtils.h" #include "../runtime/RuntimeEnvironment.h" +#include "HookModule.h" #include "BlackBone/Process/Process.h" #include "BlackBone/Process/RPC/RemoteFunction.hpp" #include @@ -13,12 +14,8 @@ std::mutex g_mutex; std::unordered_map g_bind_refs; std::wstring resolve_hook_dll(blackbone::Process &proc) { - std::wstring dll_name = RuntimeEnvironment::getOpName(); const BOOL target_is64 = proc.modules().GetMainModule()->type == blackbone::eModType::mt_mod64; - if (target_is64 != OP64) { - dll_name = target_is64 ? L"op_x64.dll" : L"op_x86.dll"; - } - return dll_name; + return op::hook::ResolveHookModuleName(target_is64 != FALSE); } long call_set_input_hook(HWND hwnd, int mode) { @@ -58,7 +55,7 @@ long call_set_input_hook(HWND hwnd, int mode) { auto call_ret = remote(hwnd, mode); ret = call_ret.result(); } else { - setlog(L"remote function 'SetInputHook' not found."); + setlog(L"remote function 'SetInputHook' not found in %s.", dll_name.c_str()); } } @@ -87,7 +84,7 @@ long call_release_input_hook(HWND hwnd) { auto call_ret = remote(); ret = call_ret.result(); } else { - setlog(L"remote function 'ReleaseInputHook' not found."); + setlog(L"remote function 'ReleaseInputHook' not found in %s.", dll_name.c_str()); } proc.Detach(); diff --git a/libop/input/keyboard/DxKeyboard.cpp b/libop/input/keyboard/DxKeyboard.cpp index 223ff93..575fddf 100644 --- a/libop/input/keyboard/DxKeyboard.cpp +++ b/libop/input/keyboard/DxKeyboard.cpp @@ -132,4 +132,8 @@ long DxKeyboard::KeyPress(long vk_code) { return KeyUp(vk_code); } +long DxKeyboard::InputChar(wchar_t ch) { + return send_input_message(_hwnd, OP_WM_CHAR, static_cast(ch), 1); +} + } // namespace op::input diff --git a/libop/input/keyboard/DxKeyboard.h b/libop/input/keyboard/DxKeyboard.h index 5938767..0849f59 100644 --- a/libop/input/keyboard/DxKeyboard.h +++ b/libop/input/keyboard/DxKeyboard.h @@ -17,6 +17,7 @@ class DxKeyboard : public KeyboardBackend { long KeyUp(long vk_code) override; long WaitKey(long vk_code, unsigned long time_out) override; long KeyPress(long vk_code) override; + long InputChar(wchar_t ch) override; private: std::array _keys{}; diff --git a/libop/input/keyboard/KeyboardBackend.h b/libop/input/keyboard/KeyboardBackend.h index 5cc73b5..67bde7e 100644 --- a/libop/input/keyboard/KeyboardBackend.h +++ b/libop/input/keyboard/KeyboardBackend.h @@ -25,6 +25,8 @@ class KeyboardBackend { virtual long KeyPress(long vk_code) = 0; + virtual long InputChar(wchar_t ch) = 0; + protected: HWND _hwnd; int _mode; diff --git a/libop/input/keyboard/WinKeyboard.cpp b/libop/input/keyboard/WinKeyboard.cpp index 3dad73b..8c92cde 100644 --- a/libop/input/keyboard/WinKeyboard.cpp +++ b/libop/input/keyboard/WinKeyboard.cpp @@ -313,4 +313,23 @@ long WinKeyboard::KeyPress(long vk_code) { return KeyUp(vk_code); } +long WinKeyboard::InputChar(wchar_t ch) { + switch (_mode) { + case INPUT_TYPE::IN_NORMAL: + case INPUT_TYPE::IN_NORMAL2: { + INPUT inputs[2] = {}; + inputs[0].type = INPUT_KEYBOARD; + inputs[0].ki.wVk = 0; + inputs[0].ki.wScan = static_cast(ch); + inputs[0].ki.dwFlags = KEYEVENTF_UNICODE; + inputs[1] = inputs[0]; + inputs[1].ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP; + return ::SendInput(2, inputs, sizeof(INPUT)) == 2 ? 1 : 0; + } + case INPUT_TYPE::IN_WINDOWS: + return send_message_result(_hwnd, WM_CHAR, static_cast(ch), 1); + } + return 0; +} + } // namespace op::input diff --git a/libop/input/keyboard/WinKeyboard.h b/libop/input/keyboard/WinKeyboard.h index 6f65582..db2dfdf 100644 --- a/libop/input/keyboard/WinKeyboard.h +++ b/libop/input/keyboard/WinKeyboard.h @@ -27,6 +27,8 @@ class WinKeyboard : public KeyboardBackend { virtual long KeyPress(long vk_code); + virtual long InputChar(wchar_t ch); + private: bool _shift_down = false; bool _ctrl_down = false; diff --git a/libop/input/mouse/DxMouse.cpp b/libop/input/mouse/DxMouse.cpp index 71370c8..3f23983 100644 --- a/libop/input/mouse/DxMouse.cpp +++ b/libop/input/mouse/DxMouse.cpp @@ -36,6 +36,7 @@ long DxMouse::Bind(HWND h, int mode) { _hwnd = h; _mode = mode; _x = _y = 0; + _button_state = 0; return ret; } @@ -44,6 +45,7 @@ long DxMouse::UnBind() { _hwnd = 0; _mode = 0; _x = _y = 0; + _button_state = 0; return ret; } @@ -77,104 +79,169 @@ long DxMouse::MoveR(int rx, int ry) { long DxMouse::MoveTo(int x, int y) { const POINT pt{x, y}; - long ret = send_op_message(_hwnd, OP_WM_MOUSEMOVE, 0, MAKELPARAM(pt.x, pt.y)); + long ret = send_op_message(_hwnd, OP_WM_MOUSEMOVE, button_state(), MAKELPARAM(pt.x, pt.y)); _x = pt.x, _y = pt.y; return ret; } -long DxMouse::MoveToEx(int x, int y, int w, int h, int &dst_x, int &dst_y) { - auto random_offset = [](int value) { - if (value == 0) - return 0; - const int span = std::abs(value); - const int offset = rand() % span; - return value > 0 ? offset : -offset; - }; +long DxMouse::send_button(UINT message, WPARAM button, bool down) { + const POINT pt = current_client_point(); + const WPARAM state = button_state_with(button, down); + const long ret = send_op_message(_hwnd, message, state, MAKELPARAM(pt.x, pt.y)); + if (ret) + set_button_state(button, down); + return ret; +} - dst_x = x + random_offset(w); - dst_y = y + random_offset(h); - return MoveTo(dst_x, dst_y); +long DxMouse::send_xbutton(UINT message, WORD xbutton, WPARAM button, bool down) { + const POINT pt = current_client_point(); + const WPARAM state = button_state_with(button, down); + const long ret = send_op_message(_hwnd, message, MAKEWPARAM(static_cast(state), xbutton), + MAKELPARAM(pt.x, pt.y)); + if (ret) + set_button_state(button, down); + return ret; } -long DxMouse::LeftClick() { - long ret = 0, ret2 = 0; +long DxMouse::click(long (DxMouse::*down)(), long (DxMouse::*up)()) { + const long r1 = (this->*down)(); + ::Delay(MOUSE_DX_DELAY); + const long r2 = (this->*up)(); + return r1 && r2 ? 1 : 0; +} + +long DxMouse::send_double_click(UINT message, UINT up_message, WPARAM button) { const POINT pt = current_client_point(); + const WPARAM state = button_state_with(button, true); + const long r1 = send_op_message(_hwnd, message, state, MAKELPARAM(pt.x, pt.y)); + if (r1) + set_button_state(button, true); + ::Delay(MOUSE_DX_DELAY); + const long r2 = send_button(up_message, button, false); + return r1 && r2 ? 1 : 0; +} - ret = send_op_message(_hwnd, OP_WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(pt.x, pt.y)); +long DxMouse::double_click(long (DxMouse::*click_func)(), UINT message, UINT up_message, WPARAM button) { + const long r1 = (this->*click_func)(); ::Delay(MOUSE_DX_DELAY); - ret2 = send_op_message(_hwnd, OP_WM_LBUTTONUP, 0, MAKELPARAM(pt.x, pt.y)); + const long r2 = send_double_click(message, up_message, button); + return r1 && r2 ? 1 : 0; +} - return ret && ret2 ? 1 : 0; +long DxMouse::xbutton(WORD xbutton_id, WPARAM button, bool down) { + return send_xbutton(down ? OP_WM_XBUTTONDOWN : OP_WM_XBUTTONUP, xbutton_id, button, down); } -long DxMouse::LeftDoubleClick() { - long r1, r2; - r1 = LeftClick(); +long DxMouse::xbutton_double_click(long (DxMouse::*click_func)(), WORD xbutton_id, WPARAM button) { + const long r1 = (this->*click_func)(); + ::Delay(MOUSE_DX_DELAY); + const long r2 = send_xbutton(OP_WM_XBUTTONDBLCLK, xbutton_id, button, true); ::Delay(MOUSE_DX_DELAY); - r2 = LeftClick(); - return r1 & r2 ? 1 : 0; + const long r3 = send_xbutton(OP_WM_XBUTTONUP, xbutton_id, button, false); + return r1 && r2 && r3 ? 1 : 0; } -long DxMouse::LeftDown() { +long DxMouse::wheel(UINT message, int delta) { const POINT pt = current_client_point(); - return send_op_message(_hwnd, OP_WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(pt.x, pt.y)); + return send_op_message(_hwnd, message, MAKEWPARAM(static_cast(button_state()), static_cast(delta)), + MAKELPARAM(pt.x, pt.y)); +} + +long DxMouse::LeftClick() { + return click(&DxMouse::LeftDown, &DxMouse::LeftUp); +} + +long DxMouse::LeftDoubleClick() { + return double_click(&DxMouse::LeftClick, OP_WM_LBUTTONDBLCLK, OP_WM_LBUTTONUP, MK_LBUTTON); +} + +long DxMouse::LeftDown() { + return send_button(OP_WM_LBUTTONDOWN, MK_LBUTTON, true); } long DxMouse::LeftUp() { - const POINT pt = current_client_point(); - return send_op_message(_hwnd, OP_WM_LBUTTONUP, 0, MAKELPARAM(pt.x, pt.y)); + return send_button(OP_WM_LBUTTONUP, MK_LBUTTON, false); } long DxMouse::MiddleClick() { - long r1, r2; - r1 = MiddleDown(); - ::Delay(MOUSE_DX_DELAY); - r2 = MiddleUp(); - return r1 & r2 ? 1 : 0; + return click(&DxMouse::MiddleDown, &DxMouse::MiddleUp); +} + +long DxMouse::MiddleDoubleClick() { + return double_click(&DxMouse::MiddleClick, OP_WM_MBUTTONDBLCLK, OP_WM_MBUTTONUP, MK_MBUTTON); } long DxMouse::MiddleDown() { - const POINT pt = current_client_point(); - return send_op_message(_hwnd, OP_WM_MBUTTONDOWN, MK_MBUTTON, MAKELPARAM(pt.x, pt.y)); + return send_button(OP_WM_MBUTTONDOWN, MK_MBUTTON, true); } long DxMouse::MiddleUp() { - const POINT pt = current_client_point(); - return send_op_message(_hwnd, OP_WM_MBUTTONUP, 0, MAKELPARAM(pt.x, pt.y)); + return send_button(OP_WM_MBUTTONUP, MK_MBUTTON, false); } long DxMouse::RightClick() { - long ret = 0; - long r1, r2; - const POINT pt = current_client_point(); - - r1 = send_op_message(_hwnd, OP_WM_RBUTTONDOWN, MK_RBUTTON, MAKELPARAM(pt.x, pt.y)); - ::Delay(MOUSE_DX_DELAY); - r2 = send_op_message(_hwnd, OP_WM_RBUTTONUP, 0, MAKELPARAM(pt.x, pt.y)); - ret = r1 && r2 ? 1 : 0; + return click(&DxMouse::RightDown, &DxMouse::RightUp); +} - return ret; +long DxMouse::RightDoubleClick() { + return double_click(&DxMouse::RightClick, OP_WM_RBUTTONDBLCLK, OP_WM_RBUTTONUP, MK_RBUTTON); } long DxMouse::RightDown() { - const POINT pt = current_client_point(); - return send_op_message(_hwnd, OP_WM_RBUTTONDOWN, MK_RBUTTON, MAKELPARAM(pt.x, pt.y)); + return send_button(OP_WM_RBUTTONDOWN, MK_RBUTTON, true); } long DxMouse::RightUp() { - const POINT pt = current_client_point(); - return send_op_message(_hwnd, OP_WM_RBUTTONUP, 0, MAKELPARAM(pt.x, pt.y)); + return send_button(OP_WM_RBUTTONUP, MK_RBUTTON, false); +} + +long DxMouse::XButton1Click() { + return click(&DxMouse::XButton1Down, &DxMouse::XButton1Up); +} + +long DxMouse::XButton1DoubleClick() { + return xbutton_double_click(&DxMouse::XButton1Click, XBUTTON1, MK_XBUTTON1); +} + +long DxMouse::XButton1Down() { + return xbutton(XBUTTON1, MK_XBUTTON1, true); +} + +long DxMouse::XButton1Up() { + return xbutton(XBUTTON1, MK_XBUTTON1, false); +} + +long DxMouse::XButton2Click() { + return click(&DxMouse::XButton2Down, &DxMouse::XButton2Up); +} + +long DxMouse::XButton2DoubleClick() { + return xbutton_double_click(&DxMouse::XButton2Click, XBUTTON2, MK_XBUTTON2); +} + +long DxMouse::XButton2Down() { + return xbutton(XBUTTON2, MK_XBUTTON2, true); +} + +long DxMouse::XButton2Up() { + return xbutton(XBUTTON2, MK_XBUTTON2, false); +} + +long DxMouse::Wheel(int delta) { + return wheel(OP_WM_MOUSEWHEEL, delta); +} + +long DxMouse::HWheel(int delta) { + return wheel(OP_WM_MOUSEHWHEEL, delta); } long DxMouse::WheelDown() { - const POINT pt = current_client_point(); - return send_op_message(_hwnd, OP_WM_MOUSEWHEEL, MAKEWPARAM(0, -WHEEL_DELTA), MAKELPARAM(pt.x, pt.y)); + return Wheel(-WHEEL_DELTA); } long DxMouse::WheelUp() { - const POINT pt = current_client_point(); - return send_op_message(_hwnd, OP_WM_MOUSEWHEEL, MAKEWPARAM(0, WHEEL_DELTA), MAKELPARAM(pt.x, pt.y)); + return Wheel(WHEEL_DELTA); } } // namespace op::input diff --git a/libop/input/mouse/DxMouse.h b/libop/input/mouse/DxMouse.h index fb24e45..4236c1c 100644 --- a/libop/input/mouse/DxMouse.h +++ b/libop/input/mouse/DxMouse.h @@ -21,8 +21,6 @@ class DxMouse : public WinMouse { long MoveTo(int x, int y) override; - long MoveToEx(int x, int y, int w, int h, int &dst_x, int &dst_y) override; - long LeftClick() override; long LeftDoubleClick() override; @@ -33,20 +31,53 @@ class DxMouse : public WinMouse { long MiddleClick() override; + long MiddleDoubleClick() override; + long MiddleDown() override; long MiddleUp() override; long RightClick() override; + long RightDoubleClick() override; + long RightDown() override; long RightUp() override; + long XButton1Click() override; + + long XButton1DoubleClick() override; + + long XButton1Down() override; + + long XButton1Up() override; + + long XButton2Click() override; + + long XButton2DoubleClick() override; + + long XButton2Down() override; + + long XButton2Up() override; + + long Wheel(int delta) override; + + long HWheel(int delta) override; + long WheelDown() override; long WheelUp() override; + private: + long send_button(UINT message, WPARAM button, bool down); + long send_xbutton(UINT message, WORD xbutton, WPARAM button, bool down); + long click(long (DxMouse::*down)(), long (DxMouse::*up)()); + long send_double_click(UINT message, UINT up_message, WPARAM button); + long double_click(long (DxMouse::*click)(), UINT message, UINT up_message, WPARAM button); + long xbutton(WORD xbutton, WPARAM button, bool down); + long xbutton_double_click(long (DxMouse::*click)(), WORD xbutton, WPARAM button); + long wheel(UINT message, int delta); }; } // namespace op::input diff --git a/libop/input/mouse/WinMouse.cpp b/libop/input/mouse/WinMouse.cpp index 58e4917..b245a18 100644 --- a/libop/input/mouse/WinMouse.cpp +++ b/libop/input/mouse/WinMouse.cpp @@ -12,7 +12,7 @@ long send_message_result(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) namespace op::input { -WinMouse::WinMouse() : _hwnd(NULL), _mode(0), _x(0), _y(0) { +WinMouse::WinMouse() : _hwnd(NULL), _mode(0), _x(0), _y(0), _button_state(0) { } WinMouse::~WinMouse() { @@ -23,6 +23,7 @@ long WinMouse::Bind(HWND h, int mode) { _hwnd = h; _mode = mode; _x = _y = 0; + _button_state = 0; return 1; } @@ -30,6 +31,7 @@ long WinMouse::UnBind() { _hwnd = 0; _mode = 0; _x = _y = 0; + _button_state = 0; return 1; } @@ -101,7 +103,7 @@ long WinMouse::MoveTo(int x, int y) { break; } case INPUT_TYPE::IN_WINDOWS: { - ret = send_message_result(_hwnd, WM_MOUSEMOVE, 0, MAKELPARAM(client_pt.x, client_pt.y)); + ret = send_message_result(_hwnd, WM_MOUSEMOVE, button_state(), MAKELPARAM(client_pt.x, client_pt.y)); break; } } @@ -120,6 +122,120 @@ long WinMouse::sync_system_cursor() { return ::SetCursorPos(pt.x, pt.y) ? 1 : 0; } +WPARAM WinMouse::button_state() const { + return _button_state; +} + +WPARAM WinMouse::button_state_with(WPARAM button, bool down) const { + return down ? (_button_state | button) : (_button_state & ~button); +} + +void WinMouse::set_button_state(WPARAM button, bool down) { + _button_state = button_state_with(button, down); +} + +long WinMouse::send_input_mouse(DWORD flags, DWORD mouse_data, bool sync_cursor) { + if (sync_cursor && !sync_system_cursor()) + return 0; + + INPUT input = {}; + input.type = INPUT_MOUSE; + input.mi.dwFlags = flags; + input.mi.mouseData = mouse_data; + return ::SendInput(1, &input, sizeof(INPUT)) > 0 ? 1 : 0; +} + +long WinMouse::send_input_click(DWORD down_flags, DWORD up_flags, DWORD mouse_data, long delay) { + if (!sync_system_cursor()) + return 0; + + const long r1 = send_input_mouse(down_flags, mouse_data, false); + ::Delay(delay); + const long r2 = send_input_mouse(up_flags, mouse_data, false); + return r1 && r2 ? 1 : 0; +} + +long WinMouse::send_windows_button(UINT message, WPARAM button, bool down) { + const POINT pt = current_client_point(); + const WPARAM state = button_state_with(button, down); + const long ret = send_message_result(_hwnd, message, state, MAKELPARAM(pt.x, pt.y)); + if (ret) + set_button_state(button, down); + return ret; +} + +long WinMouse::send_windows_xbutton(UINT message, WORD xbutton, WPARAM button, bool down) { + const POINT pt = current_client_point(); + const WPARAM state = button_state_with(button, down); + const long ret = send_message_result(_hwnd, message, MAKEWPARAM(static_cast(state), xbutton), + MAKELPARAM(pt.x, pt.y)); + if (ret) + set_button_state(button, down); + return ret; +} + +long WinMouse::button_click(long (WinMouse::*down)(), long (WinMouse::*up)(), long delay) { + const long r1 = (this->*down)(); + ::Delay(delay); + const long r2 = (this->*up)(); + return r1 && r2 ? 1 : 0; +} + +long WinMouse::normal_double_click(long (WinMouse::*click)(), long delay) { + const long r1 = (this->*click)(); + ::Delay(delay); + const long r2 = (this->*click)(); + return r1 && r2 ? 1 : 0; +} + +long WinMouse::button_double_click(long (WinMouse::*click)(), UINT message, UINT up_message, WPARAM button, + long delay) { + const long r1 = (this->*click)(); + ::Delay(delay); + const POINT pt = current_client_point(); + const WPARAM state = button_state_with(button, true); + const long r2 = send_message_result(_hwnd, message, state, MAKELPARAM(pt.x, pt.y)); + if (r2) + set_button_state(button, true); + ::Delay(delay); + const long r3 = send_windows_button(up_message, button, false); + return r1 && r2 && r3 ? 1 : 0; +} + +long WinMouse::xbutton(WORD xbutton_id, WPARAM button, bool down) { + switch (_mode) { + case INPUT_TYPE::IN_NORMAL: + return send_input_mouse(down ? MOUSEEVENTF_XDOWN : MOUSEEVENTF_XUP, xbutton_id); + case INPUT_TYPE::IN_WINDOWS: + return send_windows_xbutton(down ? WM_XBUTTONDOWN : WM_XBUTTONUP, xbutton_id, button, down); + } + return 0; +} + +long WinMouse::xbutton_double_click(long (WinMouse::*click)(), WORD xbutton_id, WPARAM button, long delay) { + const long r1 = (this->*click)(); + ::Delay(delay); + const long r2 = send_windows_xbutton(WM_XBUTTONDBLCLK, xbutton_id, button, true); + ::Delay(delay); + const long r3 = send_windows_xbutton(WM_XBUTTONUP, xbutton_id, button, false); + return r1 && r2 && r3 ? 1 : 0; +} + +long WinMouse::send_wheel(DWORD input_flag, UINT window_message, int delta) { + switch (_mode) { + case INPUT_TYPE::IN_NORMAL: + return send_input_mouse(input_flag, static_cast(delta)); + case INPUT_TYPE::IN_WINDOWS: { + POINT pt = current_client_point(); + ::ClientToScreen(_hwnd, &pt); + return send_message_result(_hwnd, window_message, + MAKEWPARAM(static_cast(button_state()), static_cast(delta)), + MAKELPARAM(pt.x, pt.y)); + } + } + return 0; +} + long WinMouse::MoveToEx(int x, int y, int w, int h, int &dst_x, int &dst_y) { auto random_offset = [](int value) { if (value == 0) @@ -135,67 +251,41 @@ long WinMouse::MoveToEx(int x, int y, int w, int h, int &dst_x, int &dst_y) { } long WinMouse::LeftClick() { - long ret = 0, ret2 = 0; switch (_mode) { case INPUT_TYPE::IN_NORMAL: { - if (!sync_system_cursor()) - return 0; - INPUT Input = {0}; - Input.type = INPUT_MOUSE; - Input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; - ret = ::SendInput(1, &Input, sizeof(INPUT)); - ::Delay(MOUSE_NORMAL_DELAY); - ::ZeroMemory(&Input, sizeof(INPUT)); - Input.type = INPUT_MOUSE; - Input.mi.dwFlags = MOUSEEVENTF_LEFTUP; - ret2 = ::SendInput(1, &Input, sizeof(INPUT)); - break; + return send_input_click(MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, 0, MOUSE_NORMAL_DELAY); } case INPUT_TYPE::IN_WINDOWS: { - const POINT pt = current_client_point(); - ret = send_message_result(_hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(pt.x, pt.y)); - ::Delay(MOUSE_WINDOWS_DELAY); - ret2 = send_message_result(_hwnd, WM_LBUTTONUP, 0, MAKELPARAM(pt.x, pt.y)); - break; + return button_click(&WinMouse::LeftDown, &WinMouse::LeftUp, MOUSE_WINDOWS_DELAY); } } - return ret && ret2 ? 1 : 0; + return 0; } long WinMouse::LeftDoubleClick() { - long r1, r2; - r1 = LeftClick(); switch (_mode) { case INPUT_TYPE::IN_NORMAL: { - ::Delay(MOUSE_NORMAL_DELAY); - break; + return normal_double_click(&WinMouse::LeftClick, MOUSE_NORMAL_DELAY); } case INPUT_TYPE::IN_WINDOWS: { - ::Delay(MOUSE_WINDOWS_DELAY); - break; + return button_double_click(&WinMouse::LeftClick, WM_LBUTTONDBLCLK, WM_LBUTTONUP, MK_LBUTTON, + MOUSE_WINDOWS_DELAY); } } - r2 = LeftClick(); - return r1 && r2 ? 1 : 0; + return 0; } long WinMouse::LeftDown() { long ret = 0; switch (_mode) { case INPUT_TYPE::IN_NORMAL: { - if (!sync_system_cursor()) - return 0; - INPUT Input = {0}; - Input.type = INPUT_MOUSE; - Input.mi.dwFlags = MOUSEEVENTF_LEFTDOWN; - ret = ::SendInput(1, &Input, sizeof(INPUT)); + ret = send_input_mouse(MOUSEEVENTF_LEFTDOWN); break; } case INPUT_TYPE::IN_WINDOWS: { - const POINT pt = current_client_point(); - ret = send_message_result(_hwnd, WM_LBUTTONDOWN, MK_LBUTTON, MAKELPARAM(pt.x, pt.y)); + ret = send_windows_button(WM_LBUTTONDOWN, MK_LBUTTON, true); break; } } @@ -206,19 +296,12 @@ long WinMouse::LeftUp() { long ret = 0; switch (_mode) { case INPUT_TYPE::IN_NORMAL: { - if (!sync_system_cursor()) - return 0; - INPUT Input = {0}; - ::ZeroMemory(&Input, sizeof(INPUT)); - Input.type = INPUT_MOUSE; - Input.mi.dwFlags = MOUSEEVENTF_LEFTUP; - ret = ::SendInput(1, &Input, sizeof(INPUT)); + ret = send_input_mouse(MOUSEEVENTF_LEFTUP); break; } case INPUT_TYPE::IN_WINDOWS: { - const POINT pt = current_client_point(); - ret = send_message_result(_hwnd, WM_LBUTTONUP, 0, MAKELPARAM(pt.x, pt.y)); + ret = send_windows_button(WM_LBUTTONUP, MK_LBUTTON, false); break; } } @@ -226,38 +309,37 @@ long WinMouse::LeftUp() { } long WinMouse::MiddleClick() { - long r1, r2; - r1 = MiddleDown(); switch (_mode) { - case INPUT_TYPE::IN_NORMAL: { - ::Delay(MOUSE_NORMAL_DELAY); - break; + case INPUT_TYPE::IN_NORMAL: + return send_input_click(MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP, 0, MOUSE_NORMAL_DELAY); + case INPUT_TYPE::IN_WINDOWS: + return button_click(&WinMouse::MiddleDown, &WinMouse::MiddleUp, MOUSE_WINDOWS_DELAY); } - case INPUT_TYPE::IN_WINDOWS: { - ::Delay(MOUSE_WINDOWS_DELAY); - break; + return 0; +} + +long WinMouse::MiddleDoubleClick() { + switch (_mode) { + case INPUT_TYPE::IN_NORMAL: { + return normal_double_click(&WinMouse::MiddleClick, MOUSE_NORMAL_DELAY); } + case INPUT_TYPE::IN_WINDOWS: + return button_double_click(&WinMouse::MiddleClick, WM_MBUTTONDBLCLK, WM_MBUTTONUP, MK_MBUTTON, + MOUSE_WINDOWS_DELAY); } - r2 = MiddleUp(); - return r1 && r2 ? 1 : 0; + return 0; } long WinMouse::MiddleDown() { long ret = 0; switch (_mode) { case INPUT_TYPE::IN_NORMAL: { - if (!sync_system_cursor()) - return 0; - INPUT Input = {0}; - Input.type = INPUT_MOUSE; - Input.mi.dwFlags = MOUSEEVENTF_MIDDLEDOWN; - ret = ::SendInput(1, &Input, sizeof(INPUT)); + ret = send_input_mouse(MOUSEEVENTF_MIDDLEDOWN); break; } case INPUT_TYPE::IN_WINDOWS: { - const POINT pt = current_client_point(); - ret = send_message_result(_hwnd, WM_MBUTTONDOWN, MK_MBUTTON, MAKELPARAM(pt.x, pt.y)); + ret = send_windows_button(WM_MBUTTONDOWN, MK_MBUTTON, true); break; } } @@ -268,19 +350,12 @@ long WinMouse::MiddleUp() { long ret = 0; switch (_mode) { case INPUT_TYPE::IN_NORMAL: { - if (!sync_system_cursor()) - return 0; - INPUT Input = {0}; - ::ZeroMemory(&Input, sizeof(INPUT)); - Input.type = INPUT_MOUSE; - Input.mi.dwFlags = MOUSEEVENTF_MIDDLEUP; - ret = ::SendInput(1, &Input, sizeof(INPUT)); + ret = send_input_mouse(MOUSEEVENTF_MIDDLEUP); break; } case INPUT_TYPE::IN_WINDOWS: { - const POINT pt = current_client_point(); - ret = send_message_result(_hwnd, WM_MBUTTONUP, 0, MAKELPARAM(pt.x, pt.y)); + ret = send_windows_button(WM_MBUTTONUP, MK_MBUTTON, false); break; } } @@ -288,52 +363,39 @@ long WinMouse::MiddleUp() { } long WinMouse::RightClick() { - long ret = 0; - long r1, r2; switch (_mode) { case INPUT_TYPE::IN_NORMAL: { - if (!sync_system_cursor()) - return 0; - INPUT Input = {0}; - Input.type = INPUT_MOUSE; - Input.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; - r1 = ::SendInput(1, &Input, sizeof(INPUT)); - ::Delay(MOUSE_NORMAL_DELAY); - ::ZeroMemory(&Input, sizeof(INPUT)); - Input.type = INPUT_MOUSE; - Input.mi.dwFlags = MOUSEEVENTF_RIGHTUP; - r2 = ::SendInput(1, &Input, sizeof(INPUT)); - ret = r1 > 0 && r2 > 0 ? 1 : 0; - break; + return send_input_click(MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP, 0, MOUSE_NORMAL_DELAY); } case INPUT_TYPE::IN_WINDOWS: { - const POINT pt = current_client_point(); - r1 = send_message_result(_hwnd, WM_RBUTTONDOWN, MK_RBUTTON, MAKELPARAM(pt.x, pt.y)); - ::Delay(MOUSE_WINDOWS_DELAY); - r2 = send_message_result(_hwnd, WM_RBUTTONUP, 0, MAKELPARAM(pt.x, pt.y)); - ret = r1 && r2 ? 1 : 0; - break; + return button_click(&WinMouse::RightDown, &WinMouse::RightUp, MOUSE_WINDOWS_DELAY); } } - return ret; + return 0; +} + +long WinMouse::RightDoubleClick() { + switch (_mode) { + case INPUT_TYPE::IN_NORMAL: { + return normal_double_click(&WinMouse::RightClick, MOUSE_NORMAL_DELAY); + } + case INPUT_TYPE::IN_WINDOWS: + return button_double_click(&WinMouse::RightClick, WM_RBUTTONDBLCLK, WM_RBUTTONUP, MK_RBUTTON, + MOUSE_WINDOWS_DELAY); + } + return 0; } long WinMouse::RightDown() { long ret = 0; switch (_mode) { case INPUT_TYPE::IN_NORMAL: { - if (!sync_system_cursor()) - return 0; - INPUT Input = {0}; - Input.type = INPUT_MOUSE; - Input.mi.dwFlags = MOUSEEVENTF_RIGHTDOWN; - ret = ::SendInput(1, &Input, sizeof(INPUT)) > 0 ? 1 : 0; + ret = send_input_mouse(MOUSEEVENTF_RIGHTDOWN); break; } case INPUT_TYPE::IN_WINDOWS: { - const POINT pt = current_client_point(); - ret = send_message_result(_hwnd, WM_RBUTTONDOWN, MK_RBUTTON, MAKELPARAM(pt.x, pt.y)); + ret = send_windows_button(WM_RBUTTONDOWN, MK_RBUTTON, true); break; } } @@ -344,74 +406,90 @@ long WinMouse::RightUp() { long ret = 0; switch (_mode) { case INPUT_TYPE::IN_NORMAL: { - if (!sync_system_cursor()) - return 0; - INPUT Input = {0}; - ::ZeroMemory(&Input, sizeof(INPUT)); - Input.type = INPUT_MOUSE; - Input.mi.dwFlags = MOUSEEVENTF_RIGHTUP; - ret = ::SendInput(1, &Input, sizeof(INPUT)) > 0 ? 1 : 0; + ret = send_input_mouse(MOUSEEVENTF_RIGHTUP); break; } case INPUT_TYPE::IN_WINDOWS: { - const POINT pt = current_client_point(); - ret = send_message_result(_hwnd, WM_RBUTTONUP, 0, MAKELPARAM(pt.x, pt.y)); + ret = send_windows_button(WM_RBUTTONUP, MK_RBUTTON, false); break; } } return ret; } -long WinMouse::WheelDown() { - long ret = 0; +long WinMouse::XButton1Click() { switch (_mode) { - case INPUT_TYPE::IN_NORMAL: { - if (!sync_system_cursor()) - return 0; - INPUT Input = {0}; - Input.type = INPUT_MOUSE; - Input.mi.dwFlags = MOUSEEVENTF_WHEEL; - Input.mi.mouseData = -WHEEL_DELTA; - ret = ::SendInput(1, &Input, sizeof(INPUT)) > 0 ? 1 : 0; - break; + case INPUT_TYPE::IN_NORMAL: + return send_input_click(MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP, XBUTTON1, MOUSE_NORMAL_DELAY); + case INPUT_TYPE::IN_WINDOWS: + return button_click(&WinMouse::XButton1Down, &WinMouse::XButton1Up, MOUSE_WINDOWS_DELAY); } + return 0; +} - case INPUT_TYPE::IN_WINDOWS: { - // WM_MOUSEWHEEL 的 lParam 使用屏幕坐标。 - POINT pt = current_client_point(); - ::ClientToScreen(_hwnd, &pt); - ret = send_message_result(_hwnd, WM_MOUSEWHEEL, MAKEWPARAM(0, -WHEEL_DELTA), MAKELPARAM(pt.x, pt.y)); - break; +long WinMouse::XButton1DoubleClick() { + switch (_mode) { + case INPUT_TYPE::IN_NORMAL: { + return normal_double_click(&WinMouse::XButton1Click, MOUSE_NORMAL_DELAY); } + case INPUT_TYPE::IN_WINDOWS: + return xbutton_double_click(&WinMouse::XButton1Click, XBUTTON1, MK_XBUTTON1, MOUSE_WINDOWS_DELAY); } + return 0; +} - return ret; +long WinMouse::XButton1Down() { + return xbutton(XBUTTON1, MK_XBUTTON1, true); } -long WinMouse::WheelUp() { - long ret = 0; +long WinMouse::XButton1Up() { + return xbutton(XBUTTON1, MK_XBUTTON1, false); +} + +long WinMouse::XButton2Click() { switch (_mode) { - case INPUT_TYPE::IN_NORMAL: { - if (!sync_system_cursor()) - return 0; - INPUT Input = {0}; - Input.type = INPUT_MOUSE; - Input.mi.dwFlags = MOUSEEVENTF_WHEEL; - Input.mi.mouseData = WHEEL_DELTA; - ret = ::SendInput(1, &Input, sizeof(INPUT)) > 0 ? 1 : 0; - break; + case INPUT_TYPE::IN_NORMAL: + return send_input_click(MOUSEEVENTF_XDOWN, MOUSEEVENTF_XUP, XBUTTON2, MOUSE_NORMAL_DELAY); + case INPUT_TYPE::IN_WINDOWS: + return button_click(&WinMouse::XButton2Down, &WinMouse::XButton2Up, MOUSE_WINDOWS_DELAY); } + return 0; +} - case INPUT_TYPE::IN_WINDOWS: { - // WM_MOUSEWHEEL 的 lParam 使用屏幕坐标。 - POINT pt = current_client_point(); - ::ClientToScreen(_hwnd, &pt); - ret = send_message_result(_hwnd, WM_MOUSEWHEEL, MAKEWPARAM(0, WHEEL_DELTA), MAKELPARAM(pt.x, pt.y)); - break; +long WinMouse::XButton2DoubleClick() { + switch (_mode) { + case INPUT_TYPE::IN_NORMAL: { + return normal_double_click(&WinMouse::XButton2Click, MOUSE_NORMAL_DELAY); } + case INPUT_TYPE::IN_WINDOWS: + return xbutton_double_click(&WinMouse::XButton2Click, XBUTTON2, MK_XBUTTON2, MOUSE_WINDOWS_DELAY); } - return ret; + return 0; +} + +long WinMouse::XButton2Down() { + return xbutton(XBUTTON2, MK_XBUTTON2, true); +} + +long WinMouse::XButton2Up() { + return xbutton(XBUTTON2, MK_XBUTTON2, false); +} + +long WinMouse::Wheel(int delta) { + return send_wheel(MOUSEEVENTF_WHEEL, WM_MOUSEWHEEL, delta); +} + +long WinMouse::HWheel(int delta) { + return send_wheel(MOUSEEVENTF_HWHEEL, WM_MOUSEHWHEEL, delta); +} + +long WinMouse::WheelDown() { + return Wheel(-WHEEL_DELTA); +} + +long WinMouse::WheelUp() { + return Wheel(WHEEL_DELTA); } } // namespace op::input diff --git a/libop/input/mouse/WinMouse.h b/libop/input/mouse/WinMouse.h index 737ab86..8d101ff 100644 --- a/libop/input/mouse/WinMouse.h +++ b/libop/input/mouse/WinMouse.h @@ -33,16 +33,40 @@ class WinMouse { virtual long MiddleClick(); + virtual long MiddleDoubleClick(); + virtual long MiddleDown(); virtual long MiddleUp(); virtual long RightClick(); + virtual long RightDoubleClick(); + virtual long RightDown(); virtual long RightUp(); + virtual long XButton1Click(); + + virtual long XButton1DoubleClick(); + + virtual long XButton1Down(); + + virtual long XButton1Up(); + + virtual long XButton2Click(); + + virtual long XButton2DoubleClick(); + + virtual long XButton2Down(); + + virtual long XButton2Up(); + + virtual long Wheel(int delta); + + virtual long HWheel(int delta); + virtual long WheelDown(); virtual long WheelUp(); @@ -50,10 +74,24 @@ class WinMouse { protected: POINT current_client_point() const; long sync_system_cursor(); + WPARAM button_state() const; + WPARAM button_state_with(WPARAM button, bool down) const; + void set_button_state(WPARAM button, bool down); + long send_input_mouse(DWORD flags, DWORD mouse_data = 0, bool sync_cursor = true); + long send_input_click(DWORD down_flags, DWORD up_flags, DWORD mouse_data, long delay); + long send_windows_button(UINT message, WPARAM button, bool down); + long send_windows_xbutton(UINT message, WORD xbutton, WPARAM button, bool down); + long button_click(long (WinMouse::*down)(), long (WinMouse::*up)(), long delay); + long normal_double_click(long (WinMouse::*click)(), long delay); + long button_double_click(long (WinMouse::*click)(), UINT message, UINT up_message, WPARAM button, long delay); + long xbutton(WORD xbutton, WPARAM button, bool down); + long xbutton_double_click(long (WinMouse::*click)(), WORD xbutton, WPARAM button, long delay); + long send_wheel(DWORD input_flag, UINT window_message, int delta); HWND _hwnd; int _mode; int _x, _y; + WPARAM _button_state; }; } // namespace op::input diff --git a/libop/ocr/OcrService.cpp b/libop/ocr/OcrService.cpp index bcd2050..59d180b 100644 --- a/libop/ocr/OcrService.cpp +++ b/libop/ocr/OcrService.cpp @@ -11,7 +11,8 @@ namespace op::ocr { namespace { constexpr const char *kTesseractDefaultEndpoint = "http://127.0.0.1:8080/api/v1/ocr"; -constexpr const char *kPaddleDefaultEndpoint = "http://127.0.0.1:8081/api/v1/ocr"; +constexpr const char *kPaddleOcrDefaultEndpoint = "http://127.0.0.1:8081/api/v1/ocr"; +constexpr const char *kPaddleNcnnOcrDefaultEndpoint = "http://127.0.0.1:8082/api/v1/ocr"; constexpr const char *kOcrDefaultPathSuffix = "api/v1/ocr"; bool try_resolve_ocr_backend(const std::string &candidate, std::string &endpoint) { @@ -19,12 +20,16 @@ bool try_resolve_ocr_backend(const std::string &candidate, std::string &endpoint if (key.empty()) { return false; } - if (key == "tesseract" || key == "tess" || key == "ocr_server") { + if (key == "tesseract" || key == "tess") { endpoint = kTesseractDefaultEndpoint; return true; } + if (key == "paddle_ncnn") { + endpoint = kPaddleNcnnOcrDefaultEndpoint; + return true; + } if (key == "paddle" || key == "paddleocr" || key == "paddle_ocr") { - endpoint = kPaddleDefaultEndpoint; + endpoint = kPaddleOcrDefaultEndpoint; return true; } return false; @@ -38,7 +43,7 @@ std::string resolve_default_endpoint() { if (resolve_endpoint_candidate(getenv_trimmed("OP_OCR_BACKEND"), endpoint, try_resolve_ocr_backend)) { return endpoint; } - return kTesseractDefaultEndpoint; + return kPaddleNcnnOcrDefaultEndpoint; } int resolve_default_timeout_ms() { @@ -48,7 +53,8 @@ int resolve_default_timeout_ms() { HttpOcrService::HttpOcrService() : m_endpoint(resolve_default_endpoint()), m_timeout_ms(resolve_default_timeout_ms()) { if (!normalize_endpoint(m_endpoint, kOcrDefaultPathSuffix)) { - m_endpoint = kTesseractDefaultEndpoint; + // 配置写错时退回主 OCR 服务,避免静默切到旧 Tesseract 后端。 + m_endpoint = kPaddleNcnnOcrDefaultEndpoint; } cout << "HttpOcrService::HttpOcrService(), endpoint=" << m_endpoint << endl; } diff --git a/libop/runtime/AutomationModes.h b/libop/runtime/AutomationModes.h index af29d61..86c0a21 100644 --- a/libop/runtime/AutomationModes.h +++ b/libop/runtime/AutomationModes.h @@ -102,7 +102,7 @@ extern long MOUSE_DX_DELAY; #define MAKE_OP_VERSION(a, b, c, d) _TOSTRING(a##.##b##.##c##.##d) #ifndef OP_VERSION -#define OP_VERSION MAKE_OP_VERSION(0, 4, 7, 0) +#define OP_VERSION MAKE_OP_VERSION(0, 4, 8, 1) #endif // OP_RUNTIME_AUTOMATION_MODES_H_ // 模块句柄 // extern HINSTANCE gInstance; diff --git a/libop/runtime/WindowsVersion.h b/libop/runtime/WindowsVersion.h index 320496e..a9eaae6 100644 --- a/libop/runtime/WindowsVersion.h +++ b/libop/runtime/WindowsVersion.h @@ -13,6 +13,7 @@ struct WindowsVersion { constexpr DWORD kWindows10Build1903 = 18362; constexpr DWORD kWindows10Build2004 = 19041; constexpr DWORD kWindowsServer2022Build = 20348; +constexpr DWORD kWindows11Build22000 = 22000; // 从 ntdll 读取真实系统版本。 // 只有查询失败时才返回 false。 diff --git a/tests/mouse_keyboard_test.cpp b/tests/mouse_keyboard_test.cpp index 96a2006..c547a42 100644 --- a/tests/mouse_keyboard_test.cpp +++ b/tests/mouse_keyboard_test.cpp @@ -15,6 +15,7 @@ using namespace std; using test_support::ColorPulseWindow; using test_support::MouseEventWindow; +using test_support::SendStringWindow; namespace { @@ -401,6 +402,24 @@ TEST(MouseKeyTest, WaitKeySpecificKeyZeroTimeout) { EXPECT_TRUE(ret == 0 || ret == 'A'); } +TEST(MouseKeyTest, WindowsModeKeyPressStrSupportsUnicodeFallback) { + SendStringWindow window; + ASSERT_TRUE(window.Create()); + + op::Client op; + long ret = 0; + op.BindWindow((long)(intptr_t)window.edit, L"normal", L"windows", L"windows", 0, &ret); + ASSERT_EQ(ret, 1); + + op.KeyPressStr(L"测A", 1, &ret); + EXPECT_EQ(ret, 1); + EXPECT_EQ(window.GetEditText(), L"测A"); + + long unbind_ret = 0; + op.UnBindWindow(&unbind_ret); + EXPECT_EQ(unbind_ret, 1); +} + TEST(MouseKeyTest, WindowsModeMouseReturnAndWheel) { op::Client op; MouseEventWindow window; @@ -433,6 +452,83 @@ TEST(MouseKeyTest, WindowsModeMouseReturnAndWheel) { EXPECT_EQ(unbind_ret, 1); } +TEST(MouseKeyTest, WindowsModeAdvancedMouseButtonsAndWheel) { + op::Client op; + MouseEventWindow window; + ASSERT_TRUE(window.Create()); + + long ret = 0; + op.BindWindow((long)(intptr_t)window.hwnd, L"normal", L"windows", L"windows", 0, &ret); + ASSERT_EQ(ret, 1); + + op.MoveTo(32, 48, &ret); + ASSERT_EQ(ret, 1); + window.ResetCounts(); + + op.LeftDoubleClick(&ret); + EXPECT_EQ(ret, 1); + op.MiddleDoubleClick(&ret); + EXPECT_EQ(ret, 1); + op.RightDoubleClick(&ret); + EXPECT_EQ(ret, 1); + op.XButton1Click(&ret); + EXPECT_EQ(ret, 1); + op.XButton1DoubleClick(&ret); + EXPECT_EQ(ret, 1); + op.Wheel(2 * WHEEL_DELTA, &ret); + EXPECT_EQ(ret, 1); + op.HWheel(-WHEEL_DELTA, &ret); + EXPECT_EQ(ret, 1); + + EXPECT_GE(window.left_double, 1); + EXPECT_GE(window.middle_double, 1); + EXPECT_GE(window.right_double, 1); + EXPECT_GE(window.xbutton1_down, 2); + EXPECT_GE(window.xbutton1_up, 2); + EXPECT_GE(window.xbutton1_double, 1); + EXPECT_EQ(window.wheel_delta_sum, 2 * WHEEL_DELTA); + EXPECT_EQ(window.hwheel_delta_sum, -WHEEL_DELTA); + + long unbind_ret = 0; + op.UnBindWindow(&unbind_ret); + EXPECT_EQ(unbind_ret, 1); +} + +TEST(MouseKeyTest, WindowsModeMouseMoveCarriesLeftButtonWhileHeld) { + op::Client op; + MouseEventWindow window; + ASSERT_TRUE(window.Create()); + + long ret = 0; + op.BindWindow((long)(intptr_t)window.hwnd, L"normal", L"windows", L"windows", 0, &ret); + ASSERT_EQ(ret, 1) << "BindWindow windows mode should succeed"; + + op.MoveTo(10, 12, &ret); + ASSERT_EQ(ret, 1); + window.ResetCounts(); + + op.LeftDown(&ret); + ASSERT_EQ(ret, 1); + op.MoveTo(48, 64, &ret); + EXPECT_EQ(ret, 1); + EXPECT_GE(window.move_with_left_count, 1); + EXPECT_NE(window.last_move_wparam & MK_LBUTTON, static_cast(0)); + EXPECT_EQ(window.last_x, 48); + EXPECT_EQ(window.last_y, 64); + + op.LeftUp(&ret); + ASSERT_EQ(ret, 1); + op.MoveR(3, 4, &ret); + EXPECT_EQ(ret, 1); + EXPECT_EQ(window.last_move_wparam & MK_LBUTTON, static_cast(0)); + EXPECT_EQ(window.last_x, 51); + EXPECT_EQ(window.last_y, 68); + + long unbind_ret = 0; + op.UnBindWindow(&unbind_ret); + EXPECT_EQ(unbind_ret, 1); +} + TEST(MouseKeyTest, WindowsModeMoveToKeepsClientCoordinatesAfterResize) { op::Client op; MouseEventWindow window; @@ -559,6 +655,100 @@ TEST(MouseKeyTest, DxModeDeliversWindowAndRawInput) { EXPECT_EQ(unbind_ret, 1); } +TEST(MouseKeyTest, DxModeDeliversAdvancedMouseButtonsAndWheel) { + op::Client op; + MouseEventWindow window; + ASSERT_TRUE(window.Create()); + + long ret = 0; + op.BindWindow((long)(intptr_t)window.hwnd, L"normal", L"dx", L"windows", 0, &ret); + if (ret != 1) { + GTEST_SKIP() << "DX mouse bind unavailable on current environment"; + } + + RAWINPUTDEVICE device = {}; + device.usUsagePage = 0x01; + device.usUsage = 0x02; + device.hwndTarget = window.hwnd; + ASSERT_TRUE(::RegisterRawInputDevices(&device, 1, sizeof(RAWINPUTDEVICE))); + PumpMessagesFor(50); + + op.MoveTo(24, 36, &ret); + ASSERT_EQ(ret, 1); + PumpMessagesFor(80); + window.ResetCounts(); + + op.LeftDoubleClick(&ret); + EXPECT_EQ(ret, 1); + op.RightDoubleClick(&ret); + EXPECT_EQ(ret, 1); + op.XButton1Click(&ret); + EXPECT_EQ(ret, 1); + op.XButton1DoubleClick(&ret); + EXPECT_EQ(ret, 1); + op.Wheel(2 * WHEEL_DELTA, &ret); + EXPECT_EQ(ret, 1); + op.HWheel(WHEEL_DELTA, &ret); + EXPECT_EQ(ret, 1); + PumpMessagesFor(180); + + EXPECT_GE(window.left_double, 1); + EXPECT_GE(window.right_double, 1); + EXPECT_GE(window.xbutton1_down, 2); + EXPECT_GE(window.xbutton1_up, 2); + EXPECT_GE(window.xbutton1_double, 1); + EXPECT_EQ(window.wheel_delta_sum, 2 * WHEEL_DELTA); + EXPECT_EQ(window.hwheel_delta_sum, WHEEL_DELTA); + EXPECT_GE(window.raw_xbutton1_down, 1); + EXPECT_GE(window.raw_xbutton1_up, 1); + EXPECT_EQ(window.raw_wheel_delta_sum, 2 * WHEEL_DELTA); + EXPECT_EQ(window.raw_hwheel_delta_sum, WHEEL_DELTA); + + long unbind_ret = 0; + op.UnBindWindow(&unbind_ret); + EXPECT_EQ(unbind_ret, 1); +} + +TEST(MouseKeyTest, DxModeMouseMoveCarriesLeftButtonWhileHeld) { + op::Client op; + MouseEventWindow window; + ASSERT_TRUE(window.Create()); + + long ret = 0; + op.BindWindow((long)(intptr_t)window.hwnd, L"normal", L"dx", L"windows", 0, &ret); + if (ret != 1) { + GTEST_SKIP() << "DX mouse bind unavailable on current environment"; + } + + op.MoveTo(10, 12, &ret); + ASSERT_EQ(ret, 1); + PumpMessagesFor(80); + window.ResetCounts(); + + op.LeftDown(&ret); + ASSERT_EQ(ret, 1); + op.MoveTo(48, 64, &ret); + EXPECT_EQ(ret, 1); + PumpMessagesFor(120); + EXPECT_GE(window.move_with_left_count, 1); + EXPECT_NE(window.last_move_wparam & MK_LBUTTON, static_cast(0)); + EXPECT_EQ(window.last_x, 48); + EXPECT_EQ(window.last_y, 64); + + op.LeftUp(&ret); + ASSERT_EQ(ret, 1); + op.MoveR(3, 4, &ret); + EXPECT_EQ(ret, 1); + PumpMessagesFor(120); + EXPECT_EQ(window.last_move_wparam & MK_LBUTTON, static_cast(0)); + EXPECT_EQ(window.last_x, 51); + EXPECT_EQ(window.last_y, 68); + + long unbind_ret = 0; + op.UnBindWindow(&unbind_ret); + EXPECT_EQ(unbind_ret, 1); +} + TEST(MouseKeyTest, DxModeMoveToKeepsClientCoordinatesAfterResize) { op::Client op; MouseEventWindow window; @@ -721,3 +911,24 @@ TEST(MouseKeyTest, DxModeFeedsDirectInputBufferedData) { op.UnBindWindow(&unbind_ret); EXPECT_EQ(unbind_ret, 1); } + +TEST(MouseKeyTest, DxModeKeyPressStrSupportsUnicodeFallback) { + SendStringWindow window; + ASSERT_TRUE(window.Create()); + + op::Client op; + long ret = 0; + op.BindWindow((long)(intptr_t)window.edit, L"normal", L"windows", L"dx", 0, &ret); + if (ret != 1) { + GTEST_SKIP() << "DX keyboard bind unavailable on current environment"; + } + + op.KeyPressStr(L"测A", 1, &ret); + EXPECT_EQ(ret, 1); + PumpMessagesFor(120); + EXPECT_EQ(window.GetEditText(), L"测A"); + + long unbind_ret = 0; + op.UnBindWindow(&unbind_ret); + EXPECT_EQ(unbind_ret, 1); +} diff --git a/tests/test_support.cpp b/tests/test_support.cpp index 5ced647..d5f275f 100644 --- a/tests/test_support.cpp +++ b/tests/test_support.cpp @@ -364,6 +364,9 @@ LRESULT CALLBACK MouseEventWindow::WndProc(HWND hwnd, UINT msg, WPARAM wparam, L switch (msg) { case WM_MOUSEMOVE: self->move_count++; + self->last_move_wparam = wparam; + if (wparam & MK_LBUTTON) + self->move_with_left_count++; self->last_x = GET_X_LPARAM(lparam); self->last_y = GET_Y_LPARAM(lparam); return 0; @@ -372,25 +375,78 @@ LRESULT CALLBACK MouseEventWindow::WndProc(HWND hwnd, UINT msg, WPARAM wparam, L self->last_x = GET_X_LPARAM(lparam); self->last_y = GET_Y_LPARAM(lparam); return 0; + case WM_LBUTTONDBLCLK: + self->left_double++; + self->last_x = GET_X_LPARAM(lparam); + self->last_y = GET_Y_LPARAM(lparam); + return 0; case WM_LBUTTONUP: self->left_up++; self->last_x = GET_X_LPARAM(lparam); self->last_y = GET_Y_LPARAM(lparam); return 0; + case WM_MBUTTONDOWN: + self->middle_down++; + self->last_x = GET_X_LPARAM(lparam); + self->last_y = GET_Y_LPARAM(lparam); + return 0; + case WM_MBUTTONDBLCLK: + self->middle_double++; + self->last_x = GET_X_LPARAM(lparam); + self->last_y = GET_Y_LPARAM(lparam); + return 0; + case WM_MBUTTONUP: + self->middle_up++; + self->last_x = GET_X_LPARAM(lparam); + self->last_y = GET_Y_LPARAM(lparam); + return 0; case WM_RBUTTONDOWN: self->right_down++; self->last_x = GET_X_LPARAM(lparam); self->last_y = GET_Y_LPARAM(lparam); return 0; + case WM_RBUTTONDBLCLK: + self->right_double++; + self->last_x = GET_X_LPARAM(lparam); + self->last_y = GET_Y_LPARAM(lparam); + return 0; case WM_RBUTTONUP: self->right_up++; self->last_x = GET_X_LPARAM(lparam); self->last_y = GET_Y_LPARAM(lparam); return 0; + case WM_XBUTTONDOWN: + if (HIWORD(wparam) == XBUTTON1) + self->xbutton1_down++; + else if (HIWORD(wparam) == XBUTTON2) + self->xbutton2_down++; + self->last_x = GET_X_LPARAM(lparam); + self->last_y = GET_Y_LPARAM(lparam); + return 0; + case WM_XBUTTONDBLCLK: + if (HIWORD(wparam) == XBUTTON1) + self->xbutton1_double++; + else if (HIWORD(wparam) == XBUTTON2) + self->xbutton2_double++; + self->last_x = GET_X_LPARAM(lparam); + self->last_y = GET_Y_LPARAM(lparam); + return 0; + case WM_XBUTTONUP: + if (HIWORD(wparam) == XBUTTON1) + self->xbutton1_up++; + else if (HIWORD(wparam) == XBUTTON2) + self->xbutton2_up++; + self->last_x = GET_X_LPARAM(lparam); + self->last_y = GET_Y_LPARAM(lparam); + return 0; case WM_MOUSEWHEEL: self->wheel_count++; self->wheel_delta_sum += GET_WHEEL_DELTA_WPARAM(wparam); return 0; + case WM_MOUSEHWHEEL: + self->hwheel_count++; + self->hwheel_delta_sum += GET_WHEEL_DELTA_WPARAM(wparam); + return 0; case WM_SETCURSOR: if (self->test_cursor) { ::SetCursor(self->test_cursor); @@ -425,6 +481,12 @@ LRESULT CALLBACK MouseEventWindow::WndProc(HWND hwnd, UINT msg, WPARAM wparam, L self->raw_left_up++; if (raw.data.mouse.usButtonFlags & RI_MOUSE_WHEEL) self->raw_wheel_delta_sum += static_cast(raw.data.mouse.usButtonData); + if (raw.data.mouse.usButtonFlags & RI_MOUSE_HWHEEL) + self->raw_hwheel_delta_sum += static_cast(raw.data.mouse.usButtonData); + if (raw.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_DOWN) + self->raw_xbutton1_down++; + if (raw.data.mouse.usButtonFlags & RI_MOUSE_BUTTON_4_UP) + self->raw_xbutton1_up++; } else if (raw.header.dwType == RIM_TYPEKEYBOARD) { self->raw_keyboard_count++; if (raw.data.keyboard.Flags & RI_KEY_BREAK) @@ -438,19 +500,41 @@ LRESULT CALLBACK MouseEventWindow::WndProc(HWND hwnd, UINT msg, WPARAM wparam, L case OP_WM_LBUTTONDOWN: self->op_left_down++; return 0; + case OP_WM_LBUTTONDBLCLK: + self->op_left_double++; + return 0; case OP_WM_LBUTTONUP: self->op_left_up++; return 0; case OP_WM_RBUTTONDOWN: self->op_right_down++; return 0; + case OP_WM_RBUTTONDBLCLK: + self->op_right_double++; + return 0; case OP_WM_RBUTTONUP: self->op_right_up++; return 0; + case OP_WM_XBUTTONDOWN: + if (HIWORD(wparam) == XBUTTON1) + self->op_xbutton1_down++; + return 0; + case OP_WM_XBUTTONDBLCLK: + if (HIWORD(wparam) == XBUTTON1) + self->op_xbutton1_double++; + return 0; + case OP_WM_XBUTTONUP: + if (HIWORD(wparam) == XBUTTON1) + self->op_xbutton1_up++; + return 0; case OP_WM_MOUSEWHEEL: self->op_wheel_count++; self->op_wheel_delta_sum += static_cast(HIWORD(wparam)); return 0; + case OP_WM_MOUSEHWHEEL: + self->op_hwheel_count++; + self->op_hwheel_delta_sum += static_cast(HIWORD(wparam)); + return 0; case OP_WM_MOUSEMOVE: self->op_move_count++; self->last_x = GET_X_LPARAM(lparam); @@ -495,29 +579,54 @@ void MouseEventWindow::SetTestCursor(HCURSOR cursor) { void MouseEventWindow::ResetCounts() { left_down = 0; left_up = 0; + left_double = 0; + middle_down = 0; + middle_up = 0; + middle_double = 0; right_down = 0; right_up = 0; + right_double = 0; + xbutton1_down = 0; + xbutton1_up = 0; + xbutton1_double = 0; + xbutton2_down = 0; + xbutton2_up = 0; + xbutton2_double = 0; wheel_count = 0; wheel_delta_sum = 0; + hwheel_count = 0; + hwheel_delta_sum = 0; move_count = 0; + move_with_left_count = 0; raw_mouse_count = 0; raw_keyboard_count = 0; raw_left_down = 0; raw_left_up = 0; + raw_xbutton1_down = 0; + raw_xbutton1_up = 0; raw_wheel_delta_sum = 0; + raw_hwheel_delta_sum = 0; raw_key_down = 0; raw_key_up = 0; raw_device_info_count = 0; raw_device_name_count = 0; op_left_down = 0; op_left_up = 0; + op_left_double = 0; op_right_down = 0; op_right_up = 0; + op_right_double = 0; + op_xbutton1_down = 0; + op_xbutton1_up = 0; + op_xbutton1_double = 0; op_wheel_count = 0; op_wheel_delta_sum = 0; + op_hwheel_count = 0; + op_hwheel_delta_sum = 0; op_move_count = 0; last_x = 0; last_y = 0; + last_move_wparam = 0; } MouseEventWindow::~MouseEventWindow() { diff --git a/tests/test_support.h b/tests/test_support.h index 6326763..f9e8c99 100644 --- a/tests/test_support.h +++ b/tests/test_support.h @@ -53,15 +53,32 @@ struct MouseEventWindow { HWND hwnd = nullptr; int left_down = 0; int left_up = 0; + int left_double = 0; + int middle_down = 0; + int middle_up = 0; + int middle_double = 0; int right_down = 0; int right_up = 0; + int right_double = 0; + int xbutton1_down = 0; + int xbutton1_up = 0; + int xbutton1_double = 0; + int xbutton2_down = 0; + int xbutton2_up = 0; + int xbutton2_double = 0; int wheel_count = 0; int wheel_delta_sum = 0; + int hwheel_count = 0; + int hwheel_delta_sum = 0; int move_count = 0; + int move_with_left_count = 0; int raw_mouse_count = 0; int raw_keyboard_count = 0; int raw_left_down = 0; int raw_left_up = 0; + int raw_xbutton1_down = 0; + int raw_xbutton1_up = 0; + int raw_hwheel_delta_sum = 0; int raw_wheel_delta_sum = 0; int raw_key_down = 0; int raw_key_up = 0; @@ -71,14 +88,22 @@ struct MouseEventWindow { int op_left_down = 0; int op_left_up = 0; + int op_left_double = 0; int op_right_down = 0; int op_right_up = 0; + int op_right_double = 0; + int op_xbutton1_down = 0; + int op_xbutton1_up = 0; + int op_xbutton1_double = 0; int op_wheel_count = 0; int op_wheel_delta_sum = 0; + int op_hwheel_count = 0; + int op_hwheel_delta_sum = 0; int op_move_count = 0; long last_x = 0; long last_y = 0; + WPARAM last_move_wparam = 0; static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam); bool Create(); diff --git a/tests/wgc_test.cpp b/tests/wgc_test.cpp index d65135c..d7c1c16 100644 --- a/tests/wgc_test.cpp +++ b/tests/wgc_test.cpp @@ -148,6 +148,75 @@ TEST_F(WgcTest, NormalAutoCapturesPlainWindows) { PumpMessagesFor(200); } +TEST_F(WgcTest, NormalDxgiFirstCaptureAfterBindUsesFreshFrame) { + ColorPulseWindow window; + ASSERT_TRUE(window.Create(false)); + + SetForegroundWindow(window.hwnd); + BringWindowToTop(window.hwnd); + PumpMessagesFor(400); + + op::Client op; + long ret = 0; + op.SetShowErrorMsg(3, &ret); + + ret = 0; + op.BindWindow((long)(intptr_t)window.hwnd, L"normal.dxgi", L"windows", L"windows", 0, &ret); + ASSERT_EQ(ret, 1); + + std::wstring color; + op.GetColor(60, 60, color); + EXPECT_EQ(color, L"FF0000") << "DXGI bind 后第一次截图应使用 AcquireNextFrame 成功返回的预热帧"; + + long unbind_ret = 0; + op.UnBindWindow(&unbind_ret); + EXPECT_EQ(unbind_ret, 1); + DestroyWindow(window.hwnd); + window.hwnd = nullptr; + PumpMessagesFor(200); +} + +TEST_F(WgcTest, NormalDxgiMaximizeAfterBindCapturesClippedClientArea) { + ColorPulseWindow window; + ASSERT_TRUE(window.Create(false)); + + SetForegroundWindow(window.hwnd); + BringWindowToTop(window.hwnd); + PumpMessagesFor(400); + + op::Client op; + long ret = 0; + op.SetShowErrorMsg(3, &ret); + + ret = 0; + op.BindWindow((long)(intptr_t)window.hwnd, L"normal.dxgi", L"windows", L"windows", 0, &ret); + ASSERT_EQ(ret, 1); + + ShowWindow(window.hwnd, SW_MAXIMIZE); + PumpMessagesFor(1000); + + RECT client = {}; + ASSERT_TRUE(GetClientRect(window.hwnd, &client)); + ASSERT_GT(client.right, 20); + ASSERT_GT(client.bottom, 20); + + std::wstring color; + op.GetColor(client.right - 12, client.bottom - 12, color); + EXPECT_EQ(color, L"FF0000") << "DXGI 最大化后客户区右下角截图不应因 DWM 边界越界失败"; + + long cap_ret = 0; + const std::wstring capture_path = test_support::GetTempBmpPath(L"op_dxgi_maximized_capture.bmp"); + op.Capture(0, 0, client.right, client.bottom, capture_path.c_str(), &cap_ret); + EXPECT_EQ(cap_ret, 1) << "DXGI 最大化后整块客户区截取不应因 1px 边界越界失败"; + + long unbind_ret = 0; + op.UnBindWindow(&unbind_ret); + EXPECT_EQ(unbind_ret, 1); + DestroyWindow(window.hwnd); + window.hwnd = nullptr; + PumpMessagesFor(200); +} + TEST_F(WgcTest, NormalAutoUsesWgcForKnownBrowserClasses) { NamedClassColorWindow window; ASSERT_TRUE(window.Create(L"Chrome_WidgetWin_1")); @@ -300,6 +369,99 @@ TEST_F(WgcTest, MaximizeAfterBindCapturesFullClientArea) { PumpMessagesFor(200); } +TEST_F(WgcTest, MaximizedWindowBindCapturesFullClientArea) { + ColorPulseWindow window; + ASSERT_TRUE(window.Create(false)); + + ShowWindow(window.hwnd, SW_MAXIMIZE); + PumpMessagesFor(1000); + + RECT client = {}; + ASSERT_TRUE(GetClientRect(window.hwnd, &client)); + ASSERT_GT(client.right, 20); + ASSERT_GT(client.bottom, 20); + + op::Client op; + long ret = 0; + op.SetShowErrorMsg(3, &ret); + + ret = 0; + op.BindWindow((long)(intptr_t)window.hwnd, L"normal.wgc", L"windows", L"windows", 0, &ret); + ASSERT_EQ(ret, 1); + + std::wstring color; + op.GetColor(client.right - 12, client.bottom - 12, color); + EXPECT_EQ(color, L"FF0000") << "最大化窗口绑定后客户区右下角截图不完整"; + + long cap_ret = 0; + const std::wstring capture_path = test_support::GetTempBmpPath(L"op_wgc_bind_maximized_capture.bmp"); + op.Capture(0, 0, client.right, client.bottom, capture_path.c_str(), &cap_ret); + EXPECT_EQ(cap_ret, 1) << "最大化窗口绑定后整块客户区截取失败"; + + long unbind_ret = 0; + op.UnBindWindow(&unbind_ret); + EXPECT_EQ(unbind_ret, 1); + DestroyWindow(window.hwnd); + window.hwnd = nullptr; + PumpMessagesFor(200); +} + +TEST_F(WgcTest, RepeatedBindUnbindAndMaximizedRepeatedCapture) { + ColorPulseWindow window; + ASSERT_TRUE(window.Create(false)); + + for (int i = 0; i < 3; ++i) { + op::Client op; + long ret = 0; + op.SetShowErrorMsg(3, &ret); + + ret = 0; + op.BindWindow((long)(intptr_t)window.hwnd, L"normal.wgc", L"windows", L"windows", 0, &ret); + ASSERT_EQ(ret, 1); + + PumpMessagesFor(300); + + std::wstring color; + op.GetColor(60, 60, color); + EXPECT_EQ(color, L"FF0000"); + + long unbind_ret = 0; + op.UnBindWindow(&unbind_ret); + EXPECT_EQ(unbind_ret, 1); + PumpMessagesFor(250); + } + + ShowWindow(window.hwnd, SW_MAXIMIZE); + PumpMessagesFor(1000); + + RECT client = {}; + ASSERT_TRUE(GetClientRect(window.hwnd, &client)); + + op::Client op; + long ret = 0; + op.SetShowErrorMsg(3, &ret); + + ret = 0; + op.BindWindow((long)(intptr_t)window.hwnd, L"normal.wgc", L"windows", L"windows", 0, &ret); + ASSERT_EQ(ret, 1); + PumpMessagesFor(500); + + std::wstring first_color; + op.GetColor(client.right - 12, client.bottom - 12, first_color); + EXPECT_EQ(first_color, L"FF0000"); + + std::wstring second_color; + op.GetColor(client.right - 12, client.bottom - 12, second_color); + EXPECT_EQ(second_color, L"FF0000"); + + long unbind_ret = 0; + op.UnBindWindow(&unbind_ret); + EXPECT_EQ(unbind_ret, 1); + DestroyWindow(window.hwnd); + window.hwnd = nullptr; + PumpMessagesFor(300); +} + // This scenario is useful for local WGC stress validation, but it is still // flaky when multiple capture sessions are created back-to-back in the same process. TEST_F(WgcTest, DISABLED_CapturesAnimatedWindowLatestFrame) {