// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.ComponentModel;
using System.Runtime.InteropServices;
using MouseJumpUI.Common.Models.Drawing;
using MouseJumpUI.Common.NativeMethods;
using static MouseJumpUI.Common.NativeMethods.Core;
using static MouseJumpUI.Common.NativeMethods.User32;
namespace MouseJumpUI.Common.Helpers;
internal static class MouseHelper
{
///
/// Calculates where to move the cursor to by projecting a point from
/// the preview image onto the desktop and using that as the target location.
///
///
/// The preview image origin is (0, 0) but the desktop origin may be non-zero,
/// or even negative if the primary monitor is not the at the top-left of the
/// entire desktop rectangle, so results may contain negative coordinates.
///
internal static PointInfo GetJumpLocation(PointInfo previewLocation, SizeInfo previewSize, RectangleInfo desktopBounds)
{
return previewLocation
.Scale(previewSize.ScaleToFitRatio(desktopBounds.Size))
.Offset(desktopBounds.Location);
}
///
/// Get the current position of the cursor.
///
internal static PointInfo GetCursorPosition()
{
var lpPoint = new LPPOINT(new POINT(0, 0));
var result = User32.GetCursorPos(lpPoint);
if (!result)
{
throw new Win32Exception(
Marshal.GetLastWin32Error());
}
var point = lpPoint.ToStructure();
lpPoint.Free();
return new PointInfo(
point.x, point.y);
}
///
/// Moves the cursor to the specified location.
///
///
/// See https://github.com/mikeclayton/FancyMouse/pull/3
///
internal static void SetCursorPosition(PointInfo location)
{
// set the new cursor position *twice* - the cursor sometimes end up in
// the wrong place if we try to cross the dead space between non-aligned
// monitors - e.g. when trying to move the cursor from (a) to (b) we can
// *sometimes* - for no clear reason - end up at (c) instead.
//
// +----------------+
// |(c) (b) |
// | |
// | |
// | |
// +---------+ |
// | (a) | |
// +---------+----------------+
//
// setting the position a second time seems to fix this and moves the
// cursor to the expected location (b)
var target = location.ToPoint();
for (var i = 0; i < 2; i++)
{
var result = User32.SetCursorPos(target.X, target.Y);
if (!result)
{
throw new Win32Exception(
Marshal.GetLastWin32Error());
}
var current = MouseHelper.GetCursorPosition();
if ((current.X == target.X) || (current.Y == target.Y))
{
break;
}
}
// temporary workaround for issue #1273
MouseHelper.SimulateMouseMovementEvent(location);
}
///
/// Sends an input simulating an absolute mouse move to the new location.
///
///
/// See https://github.com/microsoft/PowerToys/issues/24523
/// https://github.com/microsoft/PowerToys/pull/24527
///
internal static void SimulateMouseMovementEvent(PointInfo location)
{
var inputs = new User32.INPUT[]
{
new(
type: INPUT_TYPE.INPUT_MOUSE,
data: new INPUT.DUMMYUNIONNAME(
mi: new MOUSEINPUT(
dx: (int)MouseHelper.CalculateAbsoluteCoordinateX(location.X),
dy: (int)MouseHelper.CalculateAbsoluteCoordinateY(location.Y),
mouseData: 0,
dwFlags: MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE,
time: 0,
dwExtraInfo: ULONG_PTR.Null))),
};
var result = User32.SendInput(
(UINT)inputs.Length,
new LPINPUT(inputs),
INPUT.Size * inputs.Length);
if (result != inputs.Length)
{
throw new Win32Exception(
Marshal.GetLastWin32Error());
}
}
private static decimal CalculateAbsoluteCoordinateX(decimal x)
{
// If MOUSEEVENTF_ABSOLUTE value is specified, dx and dy contain normalized absolute coordinates between 0 and 65,535.
// see https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput
return (x * 65535) / User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN);
}
private static decimal CalculateAbsoluteCoordinateY(decimal y)
{
// If MOUSEEVENTF_ABSOLUTE value is specified, dx and dy contain normalized absolute coordinates between 0 and 65,535.
// see https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput
return (y * 65535) / User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN);
}
}