From cdab1af35eab2aebd4659cd43aa903cbc9f5ecbb Mon Sep 17 00:00:00 2001 From: Hiroshi Murayama Date: Sat, 30 Nov 2024 22:38:19 +0900 Subject: [PATCH] moved the mouse emulation feature for addressing MSFS touch interaction issues from core.dll to hook.dll --- src/core/capturedwindow.cpp | 4 +- src/core/core.vcxproj | 3 +- src/core/core.vcxproj.filters | 9 +-- src/core/simhost.cpp | 8 ++- src/core/viewport.cpp | 34 +--------- src/hook/hook.vcxproj | 6 +- src/hook/hook.vcxproj.filters | 6 ++ src/hook/hookdll.cpp | 114 ++++++++++++++++++++++++++++++-- src/hook/hookdll.h | 7 +- src/{core => hook}/mouseemu.cpp | 20 +++--- src/{core => hook}/mouseemu.h | 8 +-- 11 files changed, 152 insertions(+), 67 deletions(-) rename src/{core => hook}/mouseemu.cpp (87%) rename src/{core => hook}/mouseemu.h (88%) diff --git a/src/core/capturedwindow.cpp b/src/core/capturedwindow.cpp index 7fe6f08..341db1b 100644 --- a/src/core/capturedwindow.cpp +++ b/src/core/capturedwindow.cpp @@ -56,7 +56,9 @@ CapturedWindow::~CapturedWindow(){ void CapturedWindow::attach_window(HWND hwnd){ if (!this->hwnd){ this->hwnd = hwnd; - hookdll_capture(hwnd, omit_system_region); + DWORD option = omit_system_region ? CAPTURE_OPT_HIDE_SYSTEM_REGION : 0; + option |= fix_touch_issue ? CAPTURE_OPT_MODIFY_TOUCH : 0; + hookdll_capture(hwnd, option); } } diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 8c33fc9..f9b89ae 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -229,7 +229,6 @@ copy $(SolutionDir)..\modules\vJoySDK\SDK\lib\amd64\vJoyInterface.dll $(TargetDi - @@ -243,6 +242,7 @@ copy $(SolutionDir)..\modules\vJoySDK\SDK\lib\amd64\vJoyInterface.dll $(TargetDi + @@ -263,7 +263,6 @@ copy $(SolutionDir)..\modules\vJoySDK\SDK\lib\amd64\vJoyInterface.dll $(TargetDi - diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index d9679cd..43e3c66 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -93,9 +93,6 @@ Header Files - - Header Files - Header Files @@ -194,9 +191,6 @@ Source Files - - Source Files - Source Files @@ -212,6 +206,9 @@ Source Files + + Source Files + diff --git a/src/core/simhost.cpp b/src/core/simhost.cpp index 0f970bd..314fa15 100644 --- a/src/core/simhost.cpp +++ b/src/core/simhost.cpp @@ -10,6 +10,7 @@ #include "simhost.h" #include "fs2020.h" #include "dcs.h" +#include "hookdll.h" static const char *simtype_dict[] = {"msfs", "dcs"}; @@ -67,9 +68,10 @@ SimHostManager::SimHostManager(MapperEngine& engine, uint64_t event_changeAircra } } } - mapper_EngineInstance()->getViewportManager()->get_mouse_emulator().set_window_for_recovery( - activeSim < 0 ? 0 : simulators.at(activeSim)->getRepresentativeWindow(), - activeSim < 0 ? mouse_emu::recovery_type::none : simulators.at(activeSim)->getRecoveryType()); + auto hwnd = activeSim < 0 ? 0 : simulators.at(activeSim)->getRepresentativeWindow(); + auto option = activeSim < 0 ? mouse_emu::recovery_type::none : simulators.at(activeSim)->getRecoveryType(); + mapper_EngineInstance()->getViewportManager()->get_mouse_emulator().set_window_for_recovery(hwnd, option); + hookdll_setWindowForRecovery(hwnd, static_cast(option)); lock.unlock(); if (activeSim != oldActiveSim){ diff --git a/src/core/viewport.cpp b/src/core/viewport.cpp index 6778e47..018ea9c 100644 --- a/src/core/viewport.cpp +++ b/src/core/viewport.cpp @@ -172,9 +172,9 @@ namespace view_utils{ // Low level mouse hook // This funciton avoids several problems to use pop out windows of FS2020 with touch display. // The all mouse messages generated touch action on the captured windows will be droped by -// this hook procedure. On the other hand, new other mouse messages wich can be handled by -// FS2020 correctly will be generated on the other thread instead of the original mouse -// messages. +// this hook procedure. On the other hand, new mouse messages wich can be handled by FS2002 +// correctly will be generated by fsmapperhook.dll instead of the original mouse messages. +// The codes which generate new mouse messages are found in src/hook/hookdll.cpp //============================================================================================ static HHOOK hookHandle = 0; @@ -197,34 +197,6 @@ static LRESULT CALLBACK mouseHookProc(int nCode, WPARAM wParam, LPARAM lParam){ View::CapturedWindowAttributes captured_window; if (the_manager->findCapturedWindow({IntPoint{mouse->pt.x, mouse->pt.y}}, captured_window)){ if ((mouse->dwExtraInfo & touch_mask) == touch_signature && captured_window.need_to_avoid_touch_probrems){ - auto now = mouse_emu::clock::now(); - if (wParam == WM_LBUTTONDOWN){ - mouse_log("down", mouse->pt.x, mouse->pt.y); - if (in_down_state){ - auto target_time = last_ops_time + delay_up; - the_manager->get_mouse_emulator().emulate(mouse_emu::event::up, last_down_point.x, last_down_point.y, target_time); - now = max(target_time, now); - } - the_manager->get_mouse_emulator().emulate(mouse_emu::event::move, mouse->pt.x, mouse->pt.y, now); - in_down_state = true; - last_down_point = mouse->pt; - last_ops_time = max(last_ops_time + delay_down, now + delay_down); - the_manager->get_mouse_emulator().emulate(mouse_emu::event::down, mouse->pt.x, mouse->pt.y, last_ops_time); - }else if (wParam == WM_LBUTTONUP){ - mouse_log("up", mouse->pt.x, mouse->pt.y); - if (!in_down_state){ - last_down_point = mouse->pt; - last_ops_time = last_ops_time + delay_down; - the_manager->get_mouse_emulator().emulate(mouse_emu::event::down, mouse->pt.x, mouse->pt.y, last_ops_time); - } - in_down_state = false; - last_ops_time = max(now + delay_up, last_ops_time + delay_up); - the_manager->get_mouse_emulator().emulate(mouse_emu::event::up, mouse->pt.x, mouse->pt.y, last_ops_time); - }else if (wParam == WM_MOUSEMOVE){ - mouse_log("move", mouse->pt.x, mouse->pt.y); - last_ops_time = max(last_ops_time, now); - the_manager->get_mouse_emulator().emulate(mouse_emu::event::move, mouse->pt.x, mouse->pt.y, last_ops_time); - } return 1; } } diff --git a/src/hook/hook.vcxproj b/src/hook/hook.vcxproj index 5c335e0..a02a521 100644 --- a/src/hook/hook.vcxproj +++ b/src/hook/hook.vcxproj @@ -132,7 +132,7 @@ true NotUsing pch.h - stdcpp17 + stdcpp20 $(SolutionDir)core;%(AdditionalIncludeDirectories) @@ -154,7 +154,7 @@ true NotUsing pch.h - stdcpp17 + stdcpp20 $(SolutionDir)core;%(AdditionalIncludeDirectories) MultiThreaded @@ -174,10 +174,12 @@ + + diff --git a/src/hook/hook.vcxproj.filters b/src/hook/hook.vcxproj.filters index 8260215..5d61850 100644 --- a/src/hook/hook.vcxproj.filters +++ b/src/hook/hook.vcxproj.filters @@ -21,6 +21,9 @@ Source Files + + Source Files + @@ -32,6 +35,9 @@ Header Files + + Header Files + diff --git a/src/hook/hookdll.cpp b/src/hook/hookdll.cpp index d349f45..1aee5ee 100644 --- a/src/hook/hookdll.cpp +++ b/src/hook/hookdll.cpp @@ -12,11 +12,15 @@ #include #include #include +#include +using std::max; +using std::min; #define NO_SOL #include "tools.h" #include "apihook.h" #include "hookdll.h" +#include "mouseemu.h" struct FatalException{ DWORD error; @@ -106,6 +110,7 @@ enum class ControllMessageDword : DWORD{ start_capture, end_capture, change_attribute, + set_window_for_recovery, }; static void assert(bool condition){ @@ -231,7 +236,7 @@ class LeadManager : public Manager{ ::hookHandle = 0; }; - bool addCapture(HWND hWnd, bool hide_system_region){ + bool addCapture(HWND hWnd, DWORD option){ LockHolder lock(mutex); if (captured_windows.count(hWnd) > 0){ // already captured @@ -252,7 +257,7 @@ class LeadManager : public Manager{ captured_windows_ctx[i].status = CapturedWindowContext::Status::CAPTURED; update_counter.from_leader++; lock.unlock(); - ::SendMessageW(hWnd, controlMessage, static_cast(ControllMessageDword::start_capture), hide_system_region); + ::SendMessageW(hWnd, controlMessage, static_cast(ControllMessageDword::start_capture), option); return true; }; @@ -290,6 +295,11 @@ class LeadManager : public Manager{ ::SendMessageW(hWnd, controlMessage, static_cast(ControllMessageDword::change_attribute), 0); return true; }; + + bool setWinodForRecovery(HWND hwnd, int type){ + LockHolder lock(mutex); + ::SendMessageW(hwnd, controlMessage, static_cast(ControllMessageDword::set_window_for_recovery), type); + } }; static std::unique_ptr leadManager; @@ -314,6 +324,13 @@ class FollowingManager : public Manager{ LONG_PTR saved_style; RECT saved_rect; int change_request_num; + bool need_to_modify_touch{false}; + mouse_emu::milliseconds delay_down{50}; + mouse_emu::milliseconds delay_up{50}; + mouse_emu::milliseconds delay_drag{150}; + int acceptable_delta = 5; + mouse_emu::clock::time_point last_ops_time = mouse_emu::clock::now(); + mouse_emu::clock::time_point last_down_time = mouse_emu::clock::now(); }; struct ChangeRequest{ bool change_position:1; @@ -329,6 +346,9 @@ class FollowingManager : public Manager{ std::mutex lmutex; int64_t local_count = 0; std::map captured_windows; + std::unique_ptr mouse_emulator; + bool is_touch_down{false}; + POINT last_touch_point; HookedApi SetWindowLongPtrW; HookedApi SetWindowPos; @@ -352,7 +372,7 @@ class FollowingManager : public Manager{ } }; - void captureWindow(HWND hWnd, bool hide_system_region){ + void captureWindow(HWND hWnd, DWORD option){ LockHolder glock(mutex); { std::unique_lock llock(lmutex); @@ -382,10 +402,17 @@ class FollowingManager : public Manager{ ctx.cy = 0; ctx.hWndInsertAfter = nullptr; ctx.change_request_num = 0; + if (option & CAPTURE_OPT_MODIFY_TOUCH && !IsTouchWindow(hWnd, nullptr)) { + ctx.need_to_modify_touch = true; + RegisterTouchWindow(hWnd, 0); + } captured_windows.emplace(hWnd, ctx); llock.unlock(); glock.unlock(); - if (hide_system_region){ + if (ctx.need_to_modify_touch && !mouse_emulator){ + mouse_emulator = std::move(mouse_emu::create_emulator()); + } + if (option & CAPTURE_OPT_HIDE_SYSTEM_REGION){ this->SetWindowLongPtrW( hWnd, GWL_STYLE, ctx.saved_style ^(WS_BORDER | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_DLGFRAME)); @@ -407,11 +434,17 @@ class FollowingManager : public Manager{ captured_windows.erase(hWnd); local_count++; llock.unlock(); + if (ctx.need_to_modify_touch){ + UnregisterTouchWindow(hWnd); + } this->SetWindowLongPtrW(hWnd, GWL_STYLE, saved_style); this->SetWindowPos( hWnd, HWND_TOP, saved_rect.left, saved_rect.top, saved_rect.right - saved_rect.left, saved_rect.bottom - saved_rect.top, SWP_FRAMECHANGED | SWP_SHOWWINDOW); + if (captured_windows.size() == 0){ + mouse_emulator = nullptr; + } }; void changeWindowAttribute(HWND hWnd){ @@ -474,6 +507,65 @@ class FollowingManager : public Manager{ } }; + void processTouchMessage(HWND hWnd, WPARAM wparam, LPARAM lparam){ + std::unique_lock llock(lmutex); + if (captured_windows.count(hWnd) == 0){ + // specified window is not captured + return; + } + auto& ctx = captured_windows.at(hWnd); + if (!ctx.need_to_modify_touch){ + // unnecessary to generate touch message + return; + } + auto now = mouse_emu::clock::now(); + if (wparam > 1){ + if (is_touch_down){ + OutputDebugStringA("touch error\n"); + is_touch_down = false; + ctx.last_ops_time = max(now, ctx.last_ops_time + ctx.delay_up); + mouse_emulator->emulate( + mouse_emu::event::up, last_touch_point.x, last_touch_point.y, + ctx.last_ops_time + mouse_emu::milliseconds(ctx.delay_up)); + } + }else{ + auto handle = reinterpret_cast(lparam); + TOUCHINPUT input; + ::GetTouchInputInfo(handle, 1, &input, sizeof(TOUCHINPUT)); + DWORD event = 0; + POINT pt{input.x / 100, input.y / 100}; + if (input.dwFlags & TOUCHEVENTF_DOWN){ + POINT current_point; + ::GetCursorPos(¤t_point); + auto delta_x = current_point.x - pt.x; + auto delta_y = current_point.y - pt.y; + if (delta_x < -ctx.acceptable_delta || delta_x > ctx.acceptable_delta || + delta_y < -ctx.acceptable_delta || delta_y > ctx.acceptable_delta){ + ctx.last_ops_time = max(now, ctx.last_ops_time); + } + mouse_emulator->emulate(mouse_emu::event::move, pt.x, pt.y, ctx.last_ops_time); + is_touch_down = true; + last_touch_point = pt; + ctx.last_ops_time = max(now, ctx.last_ops_time + ctx.delay_down); + ctx.last_down_time = ctx.last_ops_time; + mouse_emulator->emulate(mouse_emu::event::down, pt.x, pt.y, ctx.last_ops_time); + }else if (input.dwFlags & TOUCHEVENTF_UP && is_touch_down){ + is_touch_down = false; + ctx.last_ops_time = max(ctx.last_ops_time, max(now, ctx.last_down_time + ctx.delay_up)); + mouse_emulator->emulate(mouse_emu::event::up, pt.x, pt.y, ctx.last_ops_time); + }else if ((input.dwFlags & TOUCHEVENTF_MOVE) && is_touch_down){ + last_touch_point = pt; + ctx.last_ops_time = max(now, max(ctx.last_ops_time, ctx.last_down_time + ctx.delay_drag)); + mouse_emulator->emulate(mouse_emu::event::move, pt.x, pt.y, ctx.last_ops_time); + } + } + return; + } + + void setWindowForRecovery(HWND hwnd, mouse_emu::recovery_type type){ + mouse_emulator->set_window_for_recovery(hwnd, type); + } + protected: static LONG_PTR WINAPI SetWindowLongPtrW_Hook(HWND hWnd, int nIndex, LONG_PTR dwNewLong){ std::unique_lock llock(followingManager->lmutex); @@ -506,14 +598,18 @@ LRESULT CALLBACK hookProc(int nCode, WPARAM wParam, LPARAM lParam){ if (pMsg->message == followingManager->getControlMessageCode()) { auto type = static_cast(pMsg->wParam); if (type == ControllMessageDword::start_capture){ - followingManager->captureWindow(pMsg->hwnd, pMsg->lParam); + followingManager->captureWindow(pMsg->hwnd, static_cast(pMsg->lParam)); }else if (type == ControllMessageDword::end_capture){ followingManager->releaseWindow(pMsg->hwnd); }else if (type == ControllMessageDword::change_attribute){ followingManager->changeWindowAttribute(pMsg->hwnd); + }else if (type == ControllMessageDword::set_window_for_recovery){ + followingManager->setWindowForRecovery(pMsg->hwnd, static_cast(pMsg->lParam)); } }else if (pMsg->message == WM_DESTROY){ followingManager->closeWindow(pMsg->hwnd); + }else if (pMsg->message == WM_TOUCH){ + followingManager->processTouchMessage(pMsg->hwnd, pMsg->wParam, pMsg->lParam); } } } @@ -573,9 +669,9 @@ DLLEXPORT bool hookdll_stopGlobalHook(){ return true; } -DLLEXPORT bool hookdll_capture(HWND hWnd, bool hide_system_region){ +DLLEXPORT bool hookdll_capture(HWND hWnd, DWORD option){ if (leadManager){ - return leadManager->addCapture(hWnd, hide_system_region); + return leadManager->addCapture(hWnd, option); }else{ return false; } @@ -595,4 +691,8 @@ DLLEXPORT bool hookdll_changeWindowAtrribute(HWND hWnd, HWND hWndInsertAfter, in }else{ return false; } +} + +DLLEXPORT void hookdll_setWindowForRecovery(HWND hwnd, int type){ + } \ No newline at end of file diff --git a/src/hook/hookdll.h b/src/hook/hookdll.h index 7319beb..35b42eb 100644 --- a/src/hook/hookdll.h +++ b/src/hook/hookdll.h @@ -15,15 +15,20 @@ extern "C" { #define MAX_CAPTURED_WINDOW 32 +#define CAPTURE_OPT_HIDE_SYSTEM_REGION 1 +#define CAPTURE_OPT_MODIFY_TOUCH 2 + typedef void (*WINDOW_CLOSE_CALLBACK)(HWND hWnd, void* context); DLLEXPORT bool hookdll_startGlobalHook(WINDOW_CLOSE_CALLBACK callback, void* context); DLLEXPORT bool hookdll_stopGlobalHook(); -DLLEXPORT bool hookdll_capture(HWND hWnd, bool hide_system_region); +DLLEXPORT bool hookdll_capture(HWND hWnd, DWORD option); DLLEXPORT bool hookdll_uncapture(HWND hWnd); DLLEXPORT bool hookdll_changeWindowAtrribute(HWND hWnd, HWND hWndInsertAfter, int x, int y, int cx, int cy, bool show); +DLLEXPORT void hookdll_setWindowForRecovery(HWND hwnd, int type); + #ifdef __cplusplus } #endif diff --git a/src/core/mouseemu.cpp b/src/hook/mouseemu.cpp similarity index 87% rename from src/core/mouseemu.cpp rename to src/hook/mouseemu.cpp index 5da1299..173b927 100644 --- a/src/core/mouseemu.cpp +++ b/src/hook/mouseemu.cpp @@ -83,14 +83,14 @@ class emulator_imp : public emulator{ std::this_thread::sleep_until(command.time); } - // static clock::time_point base{clock::now()}; - // std::ostringstream os; - // auto ref_time = std::chrono::duration_cast(clock::now() - base); - // os << "issueMouseEvent(" << ref_time; - // os << "): [" << static_cast(command.ev) << "] x:" << command.x << ", y:" << command.y; - // os << ", screen.x: " << primary_screen_x << ", screen.y: " << primary_screen_y; - // os << std::endl; - // OutputDebugStringA(os.str().c_str()); + static clock::time_point base{clock::now()}; + std::ostringstream os; + auto ref_time = std::chrono::duration_cast(clock::now() - base); + os << "issueMouseEvent(" << ref_time; + os << "): [" << static_cast(command.ev) << "] x:" << command.x << ", y:" << command.y; + os << ", screen.x: " << primary_screen_x << ", screen.y: " << primary_screen_y; + os << std::endl; + OutputDebugStringA(os.str().c_str()); INPUT input; input.type = INPUT_MOUSE; @@ -164,8 +164,8 @@ class emulator_imp : public emulator{ INPUT input; input.type = INPUT_MOUSE; - input.mi.dx = (static_cast(x) * 65535) / primary_screen_x; - input.mi.dy = (static_cast(y) * 65535) / primary_screen_y; + input.mi.dx = static_cast((static_cast(x) * 65535) / primary_screen_x); + input.mi.dy = static_cast((static_cast(y) * 65535) / primary_screen_y); input.mi.mouseData = 0; input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; input.mi.time = 0; diff --git a/src/core/mouseemu.h b/src/hook/mouseemu.h similarity index 88% rename from src/core/mouseemu.h rename to src/hook/mouseemu.h index 396bd77..cf02486 100644 --- a/src/core/mouseemu.h +++ b/src/hook/mouseemu.h @@ -19,10 +19,10 @@ namespace mouse_emu{ }; enum class recovery_type{ - none, - left, - right, - middle, + none = 0, + left = 1, + right = 2, + middle = 3, }; using clock = std::chrono::steady_clock;