From de7de4b7bc983c25915ccb97616cb885777e334e Mon Sep 17 00:00:00 2001 From: Ilia Burakov Date: Sun, 23 Nov 2025 20:39:31 -0500 Subject: [PATCH 1/2] Uses virtual screen coordinates Updates the application to use virtual screen coordinates instead of single-screen coordinates for mouse event handling. This ensures correct behavior across multiple monitors with different resolutions or arrangements. This change affects the calculation of absolute mouse coordinates and ensures that mouse events are properly transmitted to the target window in multi-monitor setups. --- WinSyncScroll/ViewModels/MainViewModel.cs | 25 ++++++++++++----------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/WinSyncScroll/ViewModels/MainViewModel.cs b/WinSyncScroll/ViewModels/MainViewModel.cs index 63fcc9d..decc407 100644 --- a/WinSyncScroll/ViewModels/MainViewModel.cs +++ b/WinSyncScroll/ViewModels/MainViewModel.cs @@ -95,8 +95,8 @@ public sealed partial class MainViewModel : IDisposable private Task? _mouseEventProcessingLoopTask; private Task? _updateMouseHookRectsLoopTask; - private int _smCxScreen; - private int _smCyScreen; + private int _smCxVirtualScreen; + private int _smCyVirtualScreen; private static readonly int SizeOfInput = Marshal.SizeOf(); @@ -144,8 +144,8 @@ private static INPUT CreateScrollInput(nuint mouseMessageId, int absoluteX, int }; var dwFlags = mouseMessageId switch { - WinApiConstants.WM_MOUSEWHEEL => MOUSE_EVENT_FLAGS.MOUSEEVENTF_WHEEL | MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE_NOCOALESCE, - WinApiConstants.WM_MOUSEHWHEEL => MOUSE_EVENT_FLAGS.MOUSEEVENTF_HWHEEL | MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE_NOCOALESCE, + WinApiConstants.WM_MOUSEWHEEL => MOUSE_EVENT_FLAGS.MOUSEEVENTF_WHEEL | MOUSE_EVENT_FLAGS.MOUSEEVENTF_VIRTUALDESK | MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE_NOCOALESCE, + WinApiConstants.WM_MOUSEHWHEEL => MOUSE_EVENT_FLAGS.MOUSEEVENTF_HWHEEL | MOUSE_EVENT_FLAGS.MOUSEEVENTF_VIRTUALDESK | MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE_NOCOALESCE, _ => MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE, }; inputScroll.Anonymous.mi.dwFlags = dwFlags; @@ -164,7 +164,7 @@ private static INPUT CreateMoveInput(int absoluteX, int absoluteY) { type = INPUT_TYPE.INPUT_MOUSE, }; - inputMove.Anonymous.mi.dwFlags = MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE_NOCOALESCE; + inputMove.Anonymous.mi.dwFlags = MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_VIRTUALDESK | MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE_NOCOALESCE; inputMove.Anonymous.mi.time = 0; inputMove.Anonymous.mi.mouseData = 0; inputMove.Anonymous.mi.dx = absoluteX; @@ -177,8 +177,8 @@ private static INPUT CreateMoveInput(int absoluteX, int absoluteY) private (int X, int Y) CalculateAbsoluteCoordinates(int x, int y) { return ( - X: PInvoke.MulDiv(x, 65536, _smCxScreen), - Y: PInvoke.MulDiv(y, 65536, _smCyScreen) + X: PInvoke.MulDiv(x, 65536, _smCxVirtualScreen), + Y: PInvoke.MulDiv(y, 65536, _smCyVirtualScreen) ); } @@ -320,7 +320,7 @@ private async Task RunMouseEventProcessingLoopAsync( var (sourceAbsoluteX, sourceAbsoluteY) = CalculateAbsoluteCoordinates(sourceEventX, sourceEventY); var (targetAbsoluteX, targetAbsoluteY) = CalculateAbsoluteCoordinates(targetX, targetY); - _logger.LogTrace("Converted coordinates: Source=({SourceEventX},{SourceEventY}) -> ({SourceAbsoluteX},{SourceAbsoluteY}), Target=({TargetX},{TargetY}) -> ({TargetAbsoluteX},{TargetAbsoluteY}). _smCxScreen={SmCxScreen}, _smCyScreen={SmCyScreen}", + _logger.LogTrace("Converted coordinates: Source=({SourceEventX},{SourceEventY}) -> ({SourceAbsoluteX},{SourceAbsoluteY}), Target=({TargetX},{TargetY}) -> ({TargetAbsoluteX},{TargetAbsoluteY}). VirtualScreen={SmCxVirtualScreen}x{SmCyVirtualScreen}", sourceEventX, sourceEventY, sourceAbsoluteX, @@ -329,8 +329,8 @@ private async Task RunMouseEventProcessingLoopAsync( targetY, targetAbsoluteX, targetAbsoluteY, - _smCxScreen, - _smCyScreen); + _smCxVirtualScreen, + _smCyVirtualScreen); var inputMoveToTarget = CreateMoveInput(targetAbsoluteX, targetAbsoluteY); var inputScrollTarget = CreateScrollInput(buffer.MouseMessageId, targetAbsoluteX, targetAbsoluteY, delta); @@ -518,8 +518,9 @@ private void Start() { _logger.LogInformation("Starting scroll sync between \"{SourceWindow}\" and \"{TargetWindow}\"", Source.DisplayName, Target.DisplayName); - _smCxScreen = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN); - _smCyScreen = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN); + _smCxVirtualScreen = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXVIRTUALSCREEN); + _smCyVirtualScreen = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYVIRTUALSCREEN); + _logger.LogDebug("Virtual screen dimensions: {Width}x{Height}", _smCxVirtualScreen, _smCyVirtualScreen); AppState = AppState.Running; } From 550597126155cd6330ad6b6c53923955d00f8fd3 Mon Sep 17 00:00:00 2001 From: Ilia Burakov Date: Sun, 23 Nov 2025 21:04:41 -0500 Subject: [PATCH 2/2] Corrects mouse input for virtual screens Adjusts mouse coordinate calculations to account for virtual screen offsets. This ensures accurate mouse input even when the virtual screen is offset from the primary display, resolving issues with multi-monitor setups. --- WinSyncScroll/ViewModels/MainViewModel.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/WinSyncScroll/ViewModels/MainViewModel.cs b/WinSyncScroll/ViewModels/MainViewModel.cs index decc407..415b08e 100644 --- a/WinSyncScroll/ViewModels/MainViewModel.cs +++ b/WinSyncScroll/ViewModels/MainViewModel.cs @@ -95,6 +95,8 @@ public sealed partial class MainViewModel : IDisposable private Task? _mouseEventProcessingLoopTask; private Task? _updateMouseHookRectsLoopTask; + private int _smXVirtualScreen; + private int _smYVirtualScreen; private int _smCxVirtualScreen; private int _smCyVirtualScreen; @@ -176,9 +178,11 @@ private static INPUT CreateMoveInput(int absoluteX, int absoluteY) private (int X, int Y) CalculateAbsoluteCoordinates(int x, int y) { + // Convert screen coordinates to absolute coordinates for SendInput + // Formula: ((coordinate - virtualScreenOffset) * 65536) / virtualScreenSize return ( - X: PInvoke.MulDiv(x, 65536, _smCxVirtualScreen), - Y: PInvoke.MulDiv(y, 65536, _smCyVirtualScreen) + X: PInvoke.MulDiv(x - _smXVirtualScreen, 65536, _smCxVirtualScreen), + Y: PInvoke.MulDiv(y - _smYVirtualScreen, 65536, _smCyVirtualScreen) ); } @@ -518,9 +522,15 @@ private void Start() { _logger.LogInformation("Starting scroll sync between \"{SourceWindow}\" and \"{TargetWindow}\"", Source.DisplayName, Target.DisplayName); + _smXVirtualScreen = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_XVIRTUALSCREEN); + _smYVirtualScreen = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_YVIRTUALSCREEN); _smCxVirtualScreen = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXVIRTUALSCREEN); _smCyVirtualScreen = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYVIRTUALSCREEN); - _logger.LogDebug("Virtual screen dimensions: {Width}x{Height}", _smCxVirtualScreen, _smCyVirtualScreen); + _logger.LogDebug("Virtual screen: offset=({XOffset},{YOffset}), size={Width}x{Height}", + _smXVirtualScreen, + _smYVirtualScreen, + _smCxVirtualScreen, + _smCyVirtualScreen); AppState = AppState.Running; }