[MouseJump]Reduce winforms dependency, thumbnail size settings, shortcut keys(#25487)

* [Mouse Jump] - reorganise existing NativeMethods (#25482)

* Mouse Jump] - reorganise Helper classes / main form code (#25482)

* Mouse Jump] - replace use of System.Windows.Forms.Screen with Native Methods (#25482)

* Mouse Jump] - replace use of System.Windows.Forms.SystemInformation with Native Methods (#25482)

* [Mouse Jump] - replace use of System.Windows.Forms.Cursor with Native Methods (#25482)

* [Mouse Jump] - improve popup responsiveness (#25484)

* [Mouse Jump] - fixed spellchecker errors (#25484)

* [Mouse Jump] - add settings card for thumbnail size (#24564)

* [Mouse Jump] - shortcut keys to jump to centres of screens (#25069)

* [Mouse Jump] - fix spelling (#25069)

* [Mouse Jump] - fix spelling - numpad (#25069)

* [Mouse Jump] - updated "thumbnail size" settings text (#24564)
This commit is contained in:
Michael Clayton
2023-04-24 16:15:07 +01:00
committed by GitHub
parent 467fcfee2d
commit fda75e48d5
75 changed files with 2336 additions and 588 deletions

View File

@@ -99,6 +99,7 @@
^src/modules/fancyzones/lib/FancyZonesWinHookEventIDs\.h$
^src/modules/imageresizer/dll/ContextMenuHandler\.rgs$
^src/modules/imageresizer/dll/ImageResizerExt\.rgs$
^src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.SYSTEM_METRICS_INDEX.cs$
^src/modules/powerrename/testapp/PowerRenameTest\.vcxproj\.filters$
^src/modules/previewpane/PreviewPaneUnitTests/HelperFiles/MarkdownWithHTMLImageTag\.txt$
^src/modules/previewpane/UnitTests-MarkdownPreviewHandler/HelperFiles/MarkdownWithHTMLImageTag.txt$

View File

@@ -358,6 +358,7 @@ createdump
CREATESCHEDULEDTASK
CREATESTRUCT
CREATEWINDOWFAILED
CRECT
critsec
Crossdevice
CRSEL
@@ -512,6 +513,7 @@ dreamsofameaningfullife
drivedetectionwarning
dshow
DSTINVERT
DUMMYUNIONNAME
dutil
DVASPECT
DVASPECTINFO
@@ -1064,14 +1066,18 @@ LPBYTE
LPCITEMIDLIST
LPCMINVOKECOMMANDINFO
LPCREATESTRUCT
LPCRECT
LPCTSTR
LPCWSTR
lpdw
lpfn
lpmi
LPINPUT
LPMINMAXINFO
LPMONITORINFO
LPOSVERSIONINFOEXW
lprc
LPPOINT
LPRECT
LPSAFEARRAY
LPSTR
@@ -1181,6 +1187,7 @@ mockapi
MODECHANGE
modernwpf
MODESPRUNED
MONITORENUMPROC
MONITORINFO
MONITORINFOEX
MONITORINFOEXW
@@ -1334,7 +1341,7 @@ nugets
nullonfailure
numberbox
NUMLOCK
NUMPAD
numpad
nwc
Objbase
OBJID
@@ -1971,6 +1978,7 @@ TRAYMOUSEMESSAGE
triaging
TRK
trl
Tsd
TServer
TStr
TValue
@@ -2174,6 +2182,7 @@ winternl
WINTHRESHOLD
winui
winuiex
WINVER
winxamlmanager
wistd
withinrafael

View File

@@ -3,10 +3,12 @@
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Drawing;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MouseJumpUI.Drawing.Models;
using MouseJumpUI.Helpers;
using MouseJumpUI.Models.Drawing;
using MouseJumpUI.Models.Layout;
using MouseJumpUI.Models.Screen;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.UnitTests.Helpers;
@@ -14,9 +16,9 @@ namespace MouseJumpUI.UnitTests.Helpers;
public static class DrawingHelperTests
{
[TestClass]
public class CalculateLayoutInfoTests
public sealed class CalculateLayoutInfoTests
{
public class TestCase
public sealed class TestCase
{
public TestCase(LayoutConfig layoutConfig, LayoutInfo expectedResult)
{
@@ -40,13 +42,14 @@ public static class DrawingHelperTests
// | |
// +----------------+
var layoutConfig = new LayoutConfig(
virtualScreen: new(0, 0, 5120, 1440),
screenBounds: new List<Rectangle>
virtualScreenBounds: new(0, 0, 5120, 1440),
screens: new List<ScreenInfo>
{
new(0, 0, 5120, 1440),
new ScreenInfo(HMONITOR.Null, false, new(0, 0, 5120, 1440), new(0, 0, 5120, 1440)),
},
activatedLocation: new(5120 / 2, 1440 / 2),
activatedScreen: 0,
activatedScreenIndex: 0,
activatedScreenNumber: 1,
maximumFormSize: new(1600, 1200),
formPadding: new(5, 5, 5, 5),
previewPadding: new(0, 0, 0, 0));
@@ -58,7 +61,7 @@ public static class DrawingHelperTests
{
new(0, 0, 1590, 447.1875M),
},
activatedScreen: new(0, 0, 5120, 1440));
activatedScreenBounds: new(0, 0, 5120, 1440));
yield return new[] { new TestCase(layoutConfig, layoutInfo) };
// primary monitor not topmost / leftmost - if there are screens
@@ -74,14 +77,15 @@ public static class DrawingHelperTests
// | |
// +----------------+
layoutConfig = new LayoutConfig(
virtualScreen: new(-1920, -472, 7040, 1912),
screenBounds: new List<Rectangle>
virtualScreenBounds: new(-1920, -472, 7040, 1912),
screens: new List<ScreenInfo>
{
new(-1920, -472, 1920, 1080),
new(0, 0, 5120, 1440),
new ScreenInfo(HMONITOR.Null, false, new(-1920, -472, 1920, 1080), new(-1920, -472, 1920, 1080)),
new ScreenInfo(HMONITOR.Null, false, new(0, 0, 5120, 1440), new(0, 0, 5120, 1440)),
},
activatedLocation: new(-960, -236),
activatedScreen: 0,
activatedScreenIndex: 0,
activatedScreenNumber: 1,
maximumFormSize: new(1600, 1200),
formPadding: new(5, 5, 5, 5),
previewPadding: new(0, 0, 0, 0));
@@ -99,7 +103,7 @@ public static class DrawingHelperTests
new(0, 0, 433.63636M, 243.92045M),
new(433.63636M, 106.602270M, 1156.36363M, 325.22727M),
},
activatedScreen: new(-1920, -472, 1920, 1080));
activatedScreenBounds: new(-1920, -472, 1920, 1080));
yield return new[] { new TestCase(layoutConfig, layoutInfo) };
// check we handle rounding errors in scaling the preview form
@@ -115,14 +119,15 @@ public static class DrawingHelperTests
// | | 0 |
// +----------------+-------+
layoutConfig = new LayoutConfig(
virtualScreen: new(0, 0, 7168, 1440),
screenBounds: new List<Rectangle>
virtualScreenBounds: new(0, 0, 7168, 1440),
screens: new List<ScreenInfo>
{
new(6144, 0, 1024, 768),
new(0, 0, 6144, 1440),
new ScreenInfo(HMONITOR.Null, false, new(6144, 0, 1024, 768), new(6144, 0, 1024, 768)),
new ScreenInfo(HMONITOR.Null, false, new(0, 0, 6144, 1440), new(0, 0, 6144, 1440)),
},
activatedLocation: new(6656, 384),
activatedScreen: 0,
activatedScreenIndex: 0,
activatedScreenNumber: 1,
maximumFormSize: new(1600, 1200),
formPadding: new(5, 5, 5, 5),
previewPadding: new(0, 0, 0, 0));
@@ -135,7 +140,7 @@ public static class DrawingHelperTests
new(869.14285M, 0, 144.85714M, 108.642857M),
new(0, 0, 869.142857M, 203.705357M),
},
activatedScreen: new(6144, 0, 1024, 768));
activatedScreenBounds: new(6144, 0, 1024, 768));
yield return new[] { new TestCase(layoutConfig, layoutInfo) };
// check we handle rounding errors in scaling the preview form
@@ -151,14 +156,15 @@ public static class DrawingHelperTests
// | | 0 |
// +----------------+-------+
layoutConfig = new LayoutConfig(
virtualScreen: new(0, 0, 7424, 1440),
screenBounds: new List<Rectangle>
virtualScreenBounds: new(0, 0, 7424, 1440),
screens: new List<ScreenInfo>
{
new(6144, 0, 1280, 768),
new(0, 0, 6144, 1440),
new ScreenInfo(HMONITOR.Null, false, new(6144, 0, 1280, 768), new(6144, 0, 1280, 768)),
new ScreenInfo(HMONITOR.Null, false, new(0, 0, 6144, 1440), new(0, 0, 6144, 1440)),
},
activatedLocation: new(6784, 384),
activatedScreen: 0,
activatedScreenIndex: 0,
activatedScreenNumber: 1,
maximumFormSize: new(1600, 1200),
formPadding: new(5, 5, 5, 5),
previewPadding: new(0, 0, 0, 0));
@@ -176,7 +182,7 @@ public static class DrawingHelperTests
new(1051.03448M, 0, 218.96551M, 131.37931M),
new(0, 0M, 1051.03448M, 246.33620M),
},
activatedScreen: new(6144, 0, 1280, 768));
activatedScreenBounds: new(6144, 0, 1280, 768));
yield return new[] { new TestCase(layoutConfig, layoutInfo) };
}
@@ -191,7 +197,7 @@ public static class DrawingHelperTests
// (int)1280.000000000000 -> 1280
// so we'll compare the raw values, *and* convert to an int-based
// Rectangle to compare rounded values
var actual = DrawingHelper.CalculateLayoutInfo(data.LayoutConfig);
var actual = LayoutHelper.CalculateLayoutInfo(data.LayoutConfig);
var expected = data.ExpectedResult;
Assert.AreEqual(expected.FormBounds.X, actual.FormBounds.X, 0.00001M, "FormBounds.X");
Assert.AreEqual(expected.FormBounds.Y, actual.FormBounds.Y, 0.00001M, "FormBounds.Y");
@@ -213,11 +219,11 @@ public static class DrawingHelperTests
Assert.AreEqual(expected.ScreenBounds[i].ToRectangle(), actual.ScreenBounds[i].ToRectangle(), "ActivatedScreen.ToRectangle");
}
Assert.AreEqual(expected.ActivatedScreen.X, actual.ActivatedScreen.X, "ActivatedScreen.X");
Assert.AreEqual(expected.ActivatedScreen.Y, actual.ActivatedScreen.Y, "ActivatedScreen.Y");
Assert.AreEqual(expected.ActivatedScreen.Width, actual.ActivatedScreen.Width, "ActivatedScreen.Width");
Assert.AreEqual(expected.ActivatedScreen.Height, actual.ActivatedScreen.Height, "ActivatedScreen.Height");
Assert.AreEqual(expected.ActivatedScreen.ToRectangle(), actual.ActivatedScreen.ToRectangle(), "ActivatedScreen.ToRectangle");
Assert.AreEqual(expected.ActivatedScreenBounds.X, actual.ActivatedScreenBounds.X, "ActivatedScreen.X");
Assert.AreEqual(expected.ActivatedScreenBounds.Y, actual.ActivatedScreenBounds.Y, "ActivatedScreen.Y");
Assert.AreEqual(expected.ActivatedScreenBounds.Width, actual.ActivatedScreenBounds.Width, "ActivatedScreen.Width");
Assert.AreEqual(expected.ActivatedScreenBounds.Height, actual.ActivatedScreenBounds.Height, "ActivatedScreen.Height");
Assert.AreEqual(expected.ActivatedScreenBounds.ToRectangle(), actual.ActivatedScreenBounds.ToRectangle(), "ActivatedScreen.ToRectangle");
}
}
}

View File

@@ -4,8 +4,8 @@
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MouseJumpUI.Drawing.Models;
using MouseJumpUI.Helpers;
using MouseJumpUI.Models.Drawing;
namespace MouseJumpUI.UnitTests.Helpers;
@@ -13,9 +13,9 @@ namespace MouseJumpUI.UnitTests.Helpers;
public static class MouseHelperTests
{
[TestClass]
public class GetJumpLocationTests
public sealed class GetJumpLocationTests
{
public class TestCase
public sealed class TestCase
{
public TestCase(PointInfo previewLocation, SizeInfo previewSize, RectangleInfo desktopBounds, PointInfo expectedResult)
{

View File

@@ -4,17 +4,17 @@
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MouseJumpUI.Drawing.Models;
using MouseJumpUI.Models.Drawing;
namespace MouseJumpUI.UnitTests.Drawing;
namespace MouseJumpUI.UnitTests.Models.Drawing;
[TestClass]
public static class RectangleInfoTests
{
[TestClass]
public class CenterTests
public sealed class CenterTests
{
public class TestCase
public sealed class TestCase
{
public TestCase(RectangleInfo rectangle, PointInfo point, RectangleInfo expectedResult)
{
@@ -63,9 +63,9 @@ public static class RectangleInfoTests
}
[TestClass]
public class ClampTests
public sealed class ClampTests
{
public class TestCase
public sealed class TestCase
{
public TestCase(RectangleInfo inner, RectangleInfo outer, RectangleInfo expectedResult)
{

View File

@@ -4,16 +4,17 @@
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MouseJumpUI.Drawing.Models;
using MouseJumpUI.Models.Drawing;
namespace MouseJumpUI.UnitTests.Drawing;
public sealed class SizeInfoTests
[TestClass]
public static class SizeInfoTests
{
[TestClass]
public class ScaleToFitTests
public sealed class ScaleToFitTests
{
public class TestCase
public sealed class TestCase
{
public TestCase(SizeInfo obj, SizeInfo bounds, SizeInfo expectedResult)
{
@@ -58,9 +59,9 @@ public sealed class SizeInfoTests
}
[TestClass]
public class ScaleToFitRatioTests
public sealed class ScaleToFitRatioTests
{
public class TestCase
public sealed class TestCase
{
public TestCase(SizeInfo obj, SizeInfo bounds, decimal expectedResult)
{

View File

@@ -7,95 +7,16 @@ using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows.Forms;
using MouseJumpUI.Drawing.Models;
using MouseJumpUI.NativeMethods.Core;
using MouseJumpUI.NativeWrappers;
using MouseJumpUI.Models.Drawing;
using MouseJumpUI.NativeMethods;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.Helpers;
internal static class DrawingHelper
{
public static LayoutInfo CalculateLayoutInfo(
LayoutConfig layoutConfig)
{
if (layoutConfig is null)
{
throw new ArgumentNullException(nameof(layoutConfig));
}
var builder = new LayoutInfo.Builder
{
LayoutConfig = layoutConfig,
};
builder.ActivatedScreen = layoutConfig.ScreenBounds[layoutConfig.ActivatedScreen];
// work out the maximum *constrained* form size
// * can't be bigger than the activated screen
// * can't be bigger than the max form size
var maxFormSize = builder.ActivatedScreen.Size
.Intersect(layoutConfig.MaximumFormSize);
// the drawing area for screen images is inside the
// form border and inside the preview border
var maxDrawingSize = maxFormSize
.Shrink(layoutConfig.FormPadding)
.Shrink(layoutConfig.PreviewPadding);
// scale the virtual screen to fit inside the drawing bounds
var scalingRatio = layoutConfig.VirtualScreen.Size
.ScaleToFitRatio(maxDrawingSize);
// position the drawing bounds inside the preview border
var drawingBounds = layoutConfig.VirtualScreen.Size
.ScaleToFit(maxDrawingSize)
.PlaceAt(layoutConfig.PreviewPadding.Left, layoutConfig.PreviewPadding.Top);
// now we know the size of the drawing area we can work out the preview size
builder.PreviewBounds = drawingBounds.Enlarge(layoutConfig.PreviewPadding);
// ... and the form size
// * center the form to the activated position, but nudge it back
// inside the visible area of the activated screen if it falls outside
builder.FormBounds = builder.PreviewBounds.Size
.PlaceAt(0, 0)
.Enlarge(layoutConfig.FormPadding)
.Center(layoutConfig.ActivatedLocation)
.Clamp(builder.ActivatedScreen);
// now calculate the positions of each of the screen images on the preview
builder.ScreenBounds = layoutConfig.ScreenBounds
.Select(
screen => screen
.Offset(layoutConfig.VirtualScreen.Location.Size.Negate())
.Scale(scalingRatio)
.Offset(layoutConfig.PreviewPadding.Left, layoutConfig.PreviewPadding.Top))
.ToList();
return builder.Build();
}
/// <summary>
/// Resize and position the specified form.
/// </summary>
public static void PositionForm(
Form form, RectangleInfo formBounds)
{
// note - do this in two steps rather than "this.Bounds = formBounds" as there
// appears to be an issue in WinForms with dpi scaling even when using PerMonitorV2,
// where the form scaling uses either the *primary* screen scaling or the *previous*
// screen's scaling when the form is moved to a different screen. i've got no idea
// *why*, but the exact sequence of calls below seems to be a workaround...
// see https://github.com/mikeclayton/FancyMouse/issues/2
var bounds = formBounds.ToRectangle();
form.Location = bounds.Location;
_ = form.PointToScreen(Point.Empty);
form.Size = bounds.Size;
}
/// <summary>
/// Draw the preview background.
/// Draw the gradient-filled preview background.
/// </summary>
public static void DrawPreviewBackground(
Graphics previewGraphics, RectangleInfo previewBounds, IEnumerable<RectangleInfo> screenBounds)
@@ -127,6 +48,11 @@ internal static class DrawingHelper
if (desktopHdc.IsNull)
{
desktopHdc = User32.GetWindowDC(desktopHwnd);
if (desktopHdc.IsNull)
{
throw new InvalidOperationException(
$"{nameof(User32.GetWindowDC)} returned null");
}
}
}
@@ -134,7 +60,12 @@ internal static class DrawingHelper
{
if (!desktopHwnd.IsNull && !desktopHdc.IsNull)
{
_ = User32.ReleaseDC(desktopHwnd, desktopHdc);
var result = User32.ReleaseDC(desktopHwnd, desktopHdc);
if (result == 0)
{
throw new InvalidOperationException(
$"{nameof(User32.ReleaseDC)} returned {result}");
}
}
desktopHwnd = HWND.Null;
@@ -143,14 +74,20 @@ internal static class DrawingHelper
/// <summary>
/// Checks if the device context handle exists, and creates a new one from the
/// Graphics object if not.
/// specified Graphics object if not.
/// </summary>
public static void EnsurePreviewDeviceContext(Graphics previewGraphics, ref HDC previewHdc)
{
if (previewHdc.IsNull)
{
previewHdc = new HDC(previewGraphics.GetHdc());
_ = Gdi32.SetStretchBltMode(previewHdc, MouseJumpUI.NativeMethods.Gdi32.STRETCH_BLT_MODE.STRETCH_HALFTONE);
var result = Gdi32.SetStretchBltMode(previewHdc, Gdi32.STRETCH_BLT_MODE.STRETCH_HALFTONE);
if (result == 0)
{
throw new InvalidOperationException(
$"{nameof(Gdi32.SetStretchBltMode)} returned {result}");
}
}
}
@@ -170,7 +107,7 @@ internal static class DrawingHelper
/// Draw placeholder images for any non-activated screens on the preview.
/// Will release the specified device context handle if it needs to draw anything.
/// </summary>
public static void DrawPreviewPlaceholders(
public static void DrawPreviewScreenPlaceholders(
Graphics previewGraphics, IEnumerable<RectangleInfo> screenBounds)
{
// we can exclude the activated screen because we've already draw
@@ -183,7 +120,7 @@ internal static class DrawingHelper
}
/// <summary>
/// Draws screen captures from the specified desktop handle onto the target device context.
/// Draws a screen capture from the specified desktop handle onto the target device context.
/// </summary>
public static void DrawPreviewScreen(
HDC sourceHdc,
@@ -193,7 +130,7 @@ internal static class DrawingHelper
{
var source = sourceBounds.ToRectangle();
var target = targetBounds.ToRectangle();
_ = Gdi32.StretchBlt(
var result = Gdi32.StretchBlt(
targetHdc,
target.X,
target.Y,
@@ -204,7 +141,12 @@ internal static class DrawingHelper
source.Y,
source.Width,
source.Height,
MouseJumpUI.NativeMethods.Gdi32.ROP_CODE.SRCCOPY);
Gdi32.ROP_CODE.SRCCOPY);
if (!result)
{
throw new InvalidOperationException(
$"{nameof(Gdi32.StretchBlt)} returned {result.Value}");
}
}
/// <summary>
@@ -220,7 +162,7 @@ internal static class DrawingHelper
{
var source = sourceBounds[i].ToRectangle();
var target = targetBounds[i].ToRectangle();
_ = Gdi32.StretchBlt(
var result = Gdi32.StretchBlt(
targetHdc,
target.X,
target.Y,
@@ -231,7 +173,12 @@ internal static class DrawingHelper
source.Y,
source.Width,
source.Height,
MouseJumpUI.NativeMethods.Gdi32.ROP_CODE.SRCCOPY);
Gdi32.ROP_CODE.SRCCOPY);
if (!result)
{
throw new InvalidOperationException(
$"{nameof(Gdi32.StretchBlt)} returned {result.Value}");
}
}
}
}

View File

@@ -0,0 +1,92 @@
// 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;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using MouseJumpUI.Models.Drawing;
using MouseJumpUI.Models.Layout;
namespace MouseJumpUI.Helpers;
internal static class LayoutHelper
{
public static LayoutInfo CalculateLayoutInfo(
LayoutConfig layoutConfig)
{
if (layoutConfig is null)
{
throw new ArgumentNullException(nameof(layoutConfig));
}
var builder = new LayoutInfo.Builder
{
LayoutConfig = layoutConfig,
};
builder.ActivatedScreenBounds = layoutConfig.Screens[layoutConfig.ActivatedScreenIndex].Bounds;
// work out the maximum *constrained* form size
// * can't be bigger than the activated screen
// * can't be bigger than the max form size
var maxFormSize = builder.ActivatedScreenBounds.Size
.Intersect(layoutConfig.MaximumFormSize);
// the drawing area for screen images is inside the
// form border and inside the preview border
var maxDrawingSize = maxFormSize
.Shrink(layoutConfig.FormPadding)
.Shrink(layoutConfig.PreviewPadding);
// scale the virtual screen to fit inside the drawing bounds
var scalingRatio = layoutConfig.VirtualScreenBounds.Size
.ScaleToFitRatio(maxDrawingSize);
// position the drawing bounds inside the preview border
var drawingBounds = layoutConfig.VirtualScreenBounds.Size
.ScaleToFit(maxDrawingSize)
.PlaceAt(layoutConfig.PreviewPadding.Left, layoutConfig.PreviewPadding.Top);
// now we know the size of the drawing area we can work out the preview size
builder.PreviewBounds = drawingBounds.Enlarge(layoutConfig.PreviewPadding);
// ... and the form size
// * center the form to the activated position, but nudge it back
// inside the visible area of the activated screen if it falls outside
builder.FormBounds = builder.PreviewBounds
.Enlarge(layoutConfig.FormPadding)
.Center(layoutConfig.ActivatedLocation)
.Clamp(builder.ActivatedScreenBounds);
// now calculate the positions of each of the screen images on the preview
builder.ScreenBounds = layoutConfig.Screens
.Select(
screen => screen.Bounds
.Offset(layoutConfig.VirtualScreenBounds.Location.ToSize().Negate())
.Scale(scalingRatio)
.Offset(layoutConfig.PreviewPadding.Left, layoutConfig.PreviewPadding.Top))
.ToList();
return builder.Build();
}
/// <summary>
/// Resize and position the specified form.
/// </summary>
public static void PositionForm(
Form form, RectangleInfo formBounds)
{
// note - do this in two steps rather than "this.Bounds = formBounds" as there
// appears to be an issue in WinForms with dpi scaling even when using PerMonitorV2,
// where the form scaling uses either the *primary* screen scaling or the *previous*
// screen's scaling when the form is moved to a different screen. i've got no idea
// *why*, but the exact sequence of calls below seems to be a workaround...
// see https://github.com/mikeclayton/FancyMouse/issues/2
var bounds = formBounds.ToRectangle();
form.Location = bounds.Location;
_ = form.PointToScreen(Point.Empty);
form.Size = bounds.Size;
}
}

View File

@@ -2,9 +2,12 @@
// 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.Drawing;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using MouseJumpUI.Drawing.Models;
using MouseJumpUI.Models.Drawing;
using MouseJumpUI.NativeMethods;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.Helpers;
@@ -26,13 +29,33 @@ internal static class MouseHelper
.Offset(desktopBounds.Location);
}
/// <summary>
/// Get the current position of the cursor.
/// </summary>
public 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);
}
/// <summary>
/// Moves the cursor to the specified location.
/// </summary>
/// <remarks>
/// See https://github.com/mikeclayton/FancyMouse/pull/3
/// </remarks>
public static void JumpCursor(PointInfo location)
public 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
@@ -51,8 +74,18 @@ internal static class MouseHelper
// setting the position a second time seems to fix this and moves the
// cursor to the expected location (b)
var point = location.ToPoint();
Cursor.Position = point;
Cursor.Position = point;
for (var i = 0; i < 2; i++)
{
var result = User32.SetCursorPos(point.X, point.Y);
if (!result)
{
throw new Win32Exception(
Marshal.GetLastWin32Error());
}
}
// temporary workaround for issue #1273
MouseHelper.SimulateMouseMovementEvent(location);
}
/// <summary>
@@ -62,26 +95,43 @@ internal static class MouseHelper
/// See https://github.com/microsoft/PowerToys/issues/24523
/// https://github.com/microsoft/PowerToys/pull/24527
/// </remarks>
public static void SimulateMouseMovementEvent(Point location)
public static void SimulateMouseMovementEvent(PointInfo location)
{
var mouseMoveInput = new NativeMethods.INPUT
var inputs = new User32.INPUT[]
{
type = NativeMethods.INPUTTYPE.INPUT_MOUSE,
data = new NativeMethods.InputUnion
{
mi = new NativeMethods.MOUSEINPUT
{
dx = NativeMethods.CalculateAbsoluteCoordinateX(location.X),
dy = NativeMethods.CalculateAbsoluteCoordinateY(location.Y),
mouseData = 0,
dwFlags = (uint)NativeMethods.MOUSE_INPUT_FLAGS.MOUSEEVENTF_MOVE
| (uint)NativeMethods.MOUSE_INPUT_FLAGS.MOUSEEVENTF_ABSOLUTE,
time = 0,
dwExtraInfo = 0,
},
},
new(
type: User32.INPUT_TYPE.INPUT_MOUSE,
data: new User32.INPUT.DUMMYUNIONNAME(
mi: new User32.MOUSEINPUT(
dx: (int)MouseHelper.CalculateAbsoluteCoordinateX(location.X),
dy: (int)MouseHelper.CalculateAbsoluteCoordinateY(location.Y),
mouseData: 0,
dwFlags: User32.MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE | User32.MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE,
time: 0,
dwExtraInfo: ULONG_PTR.Null))),
};
var inputs = new NativeMethods.INPUT[] { mouseMoveInput };
_ = NativeMethods.SendInput(1, inputs, NativeMethods.INPUT.Size);
var result = User32.SendInput(
(uint)inputs.Length,
new User32.LPINPUT(inputs),
User32.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(User32.SYSTEM_METRICS_INDEX.SM_CXSCREEN);
}
internal 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(User32.SYSTEM_METRICS_INDEX.SM_CYSCREEN);
}
}

View File

@@ -1,111 +0,0 @@
// 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;
using System.Runtime.InteropServices;
namespace MouseJumpUI.Helpers;
// Win32 functions required for temporary workaround for issue #1273
internal static class NativeMethods
{
[DllImport("user32.dll")]
internal static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
[DllImport("user32.dll")]
internal static extern int GetSystemMetrics(SystemMetric smIndex);
[StructLayout(LayoutKind.Sequential)]
public struct INPUT
{
internal INPUTTYPE type;
internal InputUnion data;
internal static int Size
{
get { return Marshal.SizeOf(typeof(INPUT)); }
}
}
[StructLayout(LayoutKind.Explicit)]
internal struct InputUnion
{
[FieldOffset(0)]
internal MOUSEINPUT mi;
[FieldOffset(0)]
internal KEYBDINPUT ki;
[FieldOffset(0)]
internal HARDWAREINPUT hi;
}
[StructLayout(LayoutKind.Sequential)]
internal struct MOUSEINPUT
{
internal int dx;
internal int dy;
internal int mouseData;
internal uint dwFlags;
internal uint time;
internal UIntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
internal struct KEYBDINPUT
{
internal short wVk;
internal short wScan;
internal uint dwFlags;
internal int time;
internal UIntPtr dwExtraInfo;
}
[StructLayout(LayoutKind.Sequential)]
internal struct HARDWAREINPUT
{
internal int uMsg;
internal short wParamL;
internal short wParamH;
}
internal enum INPUTTYPE : uint
{
INPUT_MOUSE = 0,
INPUT_KEYBOARD = 1,
INPUT_HARDWARE = 2,
}
internal enum MOUSE_INPUT_FLAGS : uint
{
MOUSEEVENTF_MOVE = 0x0001,
MOUSEEVENTF_LEFTDOWN = 0x0002,
MOUSEEVENTF_LEFTUP = 0x0004,
MOUSEEVENTF_RIGHTDOWN = 0x0008,
MOUSEEVENTF_RIGHTUP = 0x0010,
MOUSEEVENTF_MIDDLEDOWN = 0x0020,
MOUSEEVENTF_MIDDLEUP = 0x0040,
MOUSEEVENTF_XDOWN = 0x0080,
MOUSEEVENTF_XUP = 0x0100,
MOUSEEVENTF_WHEEL = 0x0800,
MOUSEEVENTF_HWHEEL = 0x1000,
MOUSEEVENTF_MOVE_NOCOALESCE = 0x2000,
MOUSEEVENTF_VIRTUALDESK = 0x4000,
MOUSEEVENTF_ABSOLUTE = 0x8000,
}
internal enum SystemMetric
{
SM_CXSCREEN = 0,
SM_CYSCREEN = 1,
}
internal static int CalculateAbsoluteCoordinateX(int x)
{
return (x * 65536) / GetSystemMetrics(SystemMetric.SM_CXSCREEN);
}
internal static int CalculateAbsoluteCoordinateY(int y)
{
return (y * 65536) / GetSystemMetrics(SystemMetric.SM_CYSCREEN);
}
}

View File

@@ -0,0 +1,94 @@
// 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;
using System.Collections.Generic;
using System.ComponentModel;
using MouseJumpUI.Models.Drawing;
using MouseJumpUI.Models.Screen;
using MouseJumpUI.NativeMethods;
using static MouseJumpUI.NativeMethods.Core;
using static MouseJumpUI.NativeMethods.User32;
namespace MouseJumpUI.Helpers;
internal static class ScreenHelper
{
/// <summary>
/// Duplicates functionality available in System.Windows.Forms.SystemInformation
/// to reduce the dependency on WinForms
/// </summary>
public static RectangleInfo GetVirtualScreen()
{
return new(
User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_XVIRTUALSCREEN),
User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_YVIRTUALSCREEN),
User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXVIRTUALSCREEN),
User32.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYVIRTUALSCREEN));
}
public static IEnumerable<ScreenInfo> GetAllScreens()
{
// enumerate the monitors attached to the system
var hMonitors = new List<HMONITOR>();
var result = User32.EnumDisplayMonitors(
HDC.Null,
LPCRECT.Null,
(unnamedParam1, unnamedParam2, unnamedParam3, unnamedParam4) =>
{
hMonitors.Add(unnamedParam1);
return true;
},
LPARAM.Null);
if (!result)
{
throw new Win32Exception(
$"{nameof(User32.EnumDisplayMonitors)} failed with return code {result.Value}");
}
// get detailed info about each monitor
foreach (var hMonitor in hMonitors)
{
var monitorInfoPtr = new LPMONITORINFO(
new MONITORINFO((uint)MONITORINFO.Size, RECT.Empty, RECT.Empty, 0));
result = User32.GetMonitorInfoW(hMonitor, monitorInfoPtr);
if (!result)
{
throw new Win32Exception(
$"{nameof(User32.GetMonitorInfoW)} failed with return code {result.Value}");
}
var monitorInfo = monitorInfoPtr.ToStructure();
monitorInfoPtr.Free();
yield return new ScreenInfo(
handle: hMonitor,
primary: monitorInfo.dwFlags.HasFlag(User32.MONITOR_INFO_FLAGS.MONITORINFOF_PRIMARY),
displayArea: new RectangleInfo(
monitorInfo.rcMonitor.left,
monitorInfo.rcMonitor.top,
monitorInfo.rcMonitor.right - monitorInfo.rcMonitor.left,
monitorInfo.rcMonitor.bottom - monitorInfo.rcMonitor.top),
workingArea: new RectangleInfo(
monitorInfo.rcWork.left,
monitorInfo.rcWork.top,
monitorInfo.rcWork.right - monitorInfo.rcWork.left,
monitorInfo.rcWork.bottom - monitorInfo.rcWork.top));
}
}
public static HMONITOR MonitorFromPoint(
PointInfo pt)
{
var hMonitor = User32.MonitorFromPoint(
new((int)pt.X, (int)pt.Y),
User32.MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
if (hMonitor.IsNull)
{
throw new InvalidOperationException($"no monitor found for point {pt}");
}
return hMonitor;
}
}

View File

@@ -9,20 +9,28 @@ using System.Drawing.Imaging;
using System.Linq;
using System.Windows.Forms;
using ManagedCommon;
using MouseJumpUI.Drawing.Models;
using Microsoft.PowerToys.Settings.UI.Library;
using MouseJumpUI.Helpers;
using MouseJumpUI.NativeMethods.Core;
using MouseJumpUI.Models.Drawing;
using MouseJumpUI.Models.Layout;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI;
internal partial class MainForm : Form
{
public MainForm()
public MainForm(MouseJumpSettings settings)
{
this.InitializeComponent();
this.Settings = settings ?? throw new ArgumentNullException(nameof(settings));
this.ShowThumbnail();
}
public MouseJumpSettings Settings
{
get;
}
private void MainForm_Load(object sender, EventArgs e)
{
}
@@ -32,6 +40,65 @@ internal partial class MainForm : Form
if (e.KeyCode == Keys.Escape)
{
this.OnDeactivate(EventArgs.Empty);
return;
}
// map screens to their screen number in "System > Display"
var screens = ScreenHelper.GetAllScreens()
.Select((screen, index) => new { Screen = screen, Index = index, Number = index + 1 })
.ToList();
var currentLocation = MouseHelper.GetCursorPosition();
var currentScreenHandle = ScreenHelper.MonitorFromPoint(currentLocation);
var currentScreen = screens
.Single(item => item.Screen.Handle == currentScreenHandle.Value);
var targetScreenNumber = default(int?);
if (((e.KeyCode >= Keys.D1) && (e.KeyCode <= Keys.D9))
|| ((e.KeyCode >= Keys.NumPad1) && (e.KeyCode <= Keys.NumPad9)))
{
// number keys 1-9 or numpad keys 1-9 - move to the numbered screen
var screenNumber = e.KeyCode - Keys.D0;
if (screenNumber <= screens.Count)
{
targetScreenNumber = screenNumber;
}
}
else if (e.KeyCode == Keys.P)
{
// "P" - move to the primary screen
targetScreenNumber = screens.Single(item => item.Screen.Primary).Number;
}
else if (e.KeyCode == Keys.Left)
{
// move to the previous screen
targetScreenNumber = currentScreen.Number == 1
? screens.Count
: currentScreen.Number - 1;
}
else if (e.KeyCode == Keys.Right)
{
// move to the next screen
targetScreenNumber = currentScreen.Number == screens.Count
? 1
: currentScreen.Number + 1;
}
else if (e.KeyCode == Keys.Home)
{
// move to the first screen
targetScreenNumber = 1;
}
else if (e.KeyCode == Keys.End)
{
// move to the last screen
targetScreenNumber = screens.Count;
}
if (targetScreenNumber.HasValue)
{
MouseHelper.SetCursorPosition(
screens[targetScreenNumber.Value - 1].Screen.Bounds.Midpoint);
this.OnDeactivate(EventArgs.Empty);
}
}
@@ -59,15 +126,13 @@ internal partial class MainForm : Form
if (mouseEventArgs.Button == MouseButtons.Left)
{
// plain click - move mouse pointer
var virtualScreen = ScreenHelper.GetVirtualScreen();
var scaledLocation = MouseHelper.GetJumpLocation(
new PointInfo(mouseEventArgs.X, mouseEventArgs.Y),
new SizeInfo(this.Thumbnail.Size),
new RectangleInfo(SystemInformation.VirtualScreen));
virtualScreen);
Logger.LogInfo($"scaled location = {scaledLocation}");
MouseHelper.JumpCursor(scaledLocation);
// Simulate mouse input for handlers that won't just catch the Cursor change
MouseHelper.SimulateMouseMovementEvent(scaledLocation.ToPoint());
MouseHelper.SetCursorPosition(scaledLocation);
Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new Telemetry.MouseJumpTeleportCursorEvent());
}
@@ -76,57 +141,93 @@ internal partial class MainForm : Form
public void ShowThumbnail()
{
var screens = Screen.AllScreens;
foreach (var i in Enumerable.Range(0, screens.Length))
var stopwatch = Stopwatch.StartNew();
var layoutInfo = MainForm.GetLayoutInfo(this);
LayoutHelper.PositionForm(this, layoutInfo.FormBounds);
MainForm.RenderPreview(this, layoutInfo);
stopwatch.Stop();
// we have to activate the form to make sure the deactivate event fires
Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new Telemetry.MouseJumpTeleportCursorEvent());
this.Activate();
}
private static LayoutInfo GetLayoutInfo(MainForm form)
{
// map screens to their screen number in "System > Display"
var screens = ScreenHelper.GetAllScreens()
.Select((screen, index) => new { Screen = screen, Index = index, Number = index + 1 })
.ToList();
foreach (var screen in screens)
{
var screen = screens[i];
Logger.LogInfo(string.Join(
'\n',
$"screen[{i}] = \"{screen.DeviceName}\"",
$"\tprimary = {screen.Primary}",
$"\tbounds = {screen.Bounds}",
$"\tworking area = {screen.WorkingArea}"));
$"screen[{screen.Number}]",
$"\tprimary = {screen.Screen.Primary}",
$"\tdisplay area = {screen.Screen.DisplayArea}",
$"\tworking area = {screen.Screen.WorkingArea}"));
}
// collect together some values that we need for calculating layout
var activatedLocation = Cursor.Position;
var activatedLocation = MouseHelper.GetCursorPosition();
var activatedScreenHandle = ScreenHelper.MonitorFromPoint(activatedLocation);
var activatedScreenIndex = screens
.Single(item => item.Screen.Handle == activatedScreenHandle.Value)
.Index;
var layoutConfig = new LayoutConfig(
virtualScreen: SystemInformation.VirtualScreen,
screenBounds: Screen.AllScreens.Select(screen => screen.Bounds),
virtualScreenBounds: ScreenHelper.GetVirtualScreen(),
screens: screens.Select(item => item.Screen).ToList(),
activatedLocation: activatedLocation,
activatedScreen: Array.IndexOf(Screen.AllScreens, Screen.FromPoint(activatedLocation)),
maximumFormSize: new Size(1600, 1200),
formPadding: this.panel1.Padding,
previewPadding: new Padding(0));
activatedScreenIndex: activatedScreenIndex,
activatedScreenNumber: activatedScreenIndex + 1,
maximumFormSize: new(
form.Settings.Properties.ThumbnailSize.Width,
form.Settings.Properties.ThumbnailSize.Height),
formPadding: new(
form.panel1.Padding.Left,
form.panel1.Padding.Top,
form.panel1.Padding.Right,
form.panel1.Padding.Bottom),
previewPadding: new(0));
Logger.LogInfo(string.Join(
'\n',
$"Layout config",
$"-------------",
$"virtual screen = {layoutConfig.VirtualScreen}",
$"virtual screen = {layoutConfig.VirtualScreenBounds}",
$"activated location = {layoutConfig.ActivatedLocation}",
$"activated screen = {layoutConfig.ActivatedScreen}",
$"activated screen index = {layoutConfig.ActivatedScreenIndex}",
$"activated screen number = {layoutConfig.ActivatedScreenNumber}",
$"maximum form size = {layoutConfig.MaximumFormSize}",
$"form padding = {layoutConfig.FormPadding}",
$"preview padding = {layoutConfig.PreviewPadding}"));
// calculate the layout coordinates for everything
var layoutInfo = DrawingHelper.CalculateLayoutInfo(layoutConfig);
var layoutInfo = LayoutHelper.CalculateLayoutInfo(layoutConfig);
Logger.LogInfo(string.Join(
'\n',
$"Layout info",
$"-----------",
$"form bounds = {layoutInfo.FormBounds}",
$"preview bounds = {layoutInfo.PreviewBounds}",
$"activated screen = {layoutInfo.ActivatedScreen}"));
$"activated screen = {layoutInfo.ActivatedScreenBounds}"));
DrawingHelper.PositionForm(this, layoutInfo.FormBounds);
return layoutInfo;
}
private static void RenderPreview(
MainForm form, LayoutInfo layoutInfo)
{
var layoutConfig = layoutInfo.LayoutConfig;
var stopwatch = Stopwatch.StartNew();
// initialize the preview image
var preview = new Bitmap(
(int)layoutInfo.PreviewBounds.Width,
(int)layoutInfo.PreviewBounds.Height,
PixelFormat.Format32bppArgb);
this.Thumbnail.Image = preview;
form.Thumbnail.Image = preview;
using var previewGraphics = Graphics.FromImage(preview);
@@ -137,49 +238,51 @@ internal partial class MainForm : Form
var previewHdc = HDC.Null;
try
{
// sort the source and target screen areas, putting the activated screen first
// (we need to capture and draw the activated screen before we show the form
// because otherwise we'll capture the form as part of the screenshot!)
var sourceScreens = layoutConfig.Screens
.Where((_, idx) => idx == layoutConfig.ActivatedScreenIndex)
.Union(layoutConfig.Screens.Where((_, idx) => idx != layoutConfig.ActivatedScreenIndex))
.Select(screen => screen.Bounds)
.ToList();
var targetScreens = layoutInfo.ScreenBounds
.Where((_, idx) => idx == layoutConfig.ActivatedScreenIndex)
.Union(layoutInfo.ScreenBounds.Where((_, idx) => idx != layoutConfig.ActivatedScreenIndex))
.ToList();
DrawingHelper.EnsureDesktopDeviceContext(ref desktopHwnd, ref desktopHdc);
// we have to capture the screen where we're going to show the form first
// as the form will obscure the screen as soon as it's visible
var activatedStopwatch = Stopwatch.StartNew();
DrawingHelper.EnsurePreviewDeviceContext(previewGraphics, ref previewHdc);
DrawingHelper.DrawPreviewScreen(
desktopHdc,
previewHdc,
layoutConfig.ScreenBounds[layoutConfig.ActivatedScreen],
layoutInfo.ScreenBounds[layoutConfig.ActivatedScreen]);
activatedStopwatch.Stop();
// show the placeholder images if it looks like it might take a while
// to capture the remaining screenshot images
if (activatedStopwatch.ElapsedMilliseconds > 250)
var placeholdersDrawn = false;
for (var i = 0; i < sourceScreens.Count; i++)
{
var activatedArea = layoutConfig.ScreenBounds[layoutConfig.ActivatedScreen].Area;
var totalArea = layoutConfig.ScreenBounds.Sum(screen => screen.Area);
if ((activatedArea / totalArea) < 0.5M)
DrawingHelper.DrawPreviewScreen(
desktopHdc, previewHdc, sourceScreens[i], targetScreens[i]);
// show the placeholder images and show the form if it looks like it might take
// a while to capture the remaining screenshot images (but only if there are any)
if ((i < (sourceScreens.Count - 1)) && (stopwatch.ElapsedMilliseconds > 250))
{
// we need to release the device context handle before we can draw the placeholders
// we need to release the device context handle before we draw the placeholders
// using the Graphics object otherwise we'll get an error from GDI saying
// "Object is currently in use elsewhere"
DrawingHelper.FreePreviewDeviceContext(previewGraphics, ref previewHdc);
DrawingHelper.DrawPreviewPlaceholders(
if (!placeholdersDrawn)
{
// draw placeholders for any undrawn screens
DrawingHelper.DrawPreviewScreenPlaceholders(
previewGraphics,
layoutInfo.ScreenBounds.Where((_, idx) => idx != layoutConfig.ActivatedScreen));
MainForm.ShowPreview(this);
}
targetScreens.Where((_, idx) => idx > i));
placeholdersDrawn = true;
}
// draw the remaining screen captures (if any) on the preview image
var sourceScreens = layoutConfig.ScreenBounds.Where((_, idx) => idx != layoutConfig.ActivatedScreen).ToList();
if (sourceScreens.Any())
{
MainForm.RefreshPreview(form);
// we've still got more screens to draw so open the device context again
DrawingHelper.EnsurePreviewDeviceContext(previewGraphics, ref previewHdc);
DrawingHelper.DrawPreviewScreens(
desktopHdc,
previewHdc,
sourceScreens,
layoutInfo.ScreenBounds.Where((_, idx) => idx != layoutConfig.ActivatedScreen).ToList());
MainForm.ShowPreview(this);
}
}
}
finally
@@ -188,13 +291,11 @@ internal partial class MainForm : Form
DrawingHelper.FreePreviewDeviceContext(previewGraphics, ref previewHdc);
}
// we have to activate the form to make sure the deactivate event fires
MainForm.ShowPreview(this);
Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new Telemetry.MouseJumpTeleportCursorEvent());
this.Activate();
MainForm.RefreshPreview(form);
stopwatch.Stop();
}
private static void ShowPreview(MainForm form)
private static void RefreshPreview(MainForm form)
{
if (!form.Visible)
{

View File

@@ -4,7 +4,7 @@
using System.Windows.Forms;
namespace MouseJumpUI.Drawing.Models;
namespace MouseJumpUI.Models.Drawing;
/// <summary>
/// Immutable version of a System.Windows.Forms.Padding object with some extra utility methods.
@@ -16,11 +16,6 @@ public sealed class PaddingInfo
{
}
public PaddingInfo(Padding padding)
: this(padding.Left, padding.Top, padding.Right, padding.Bottom)
{
}
public PaddingInfo(decimal left, decimal top, decimal right, decimal bottom)
{
this.Left = left;

View File

@@ -4,7 +4,7 @@
using System.Drawing;
namespace MouseJumpUI.Drawing.Models;
namespace MouseJumpUI.Models.Drawing;
/// <summary>
/// Immutable version of a System.Drawing.Point object with some extra utility methods.
@@ -32,7 +32,10 @@ public sealed class PointInfo
get;
}
public SizeInfo Size => new((int)this.X, (int)this.Y);
public SizeInfo ToSize()
{
return new((int)this.X, (int)this.Y);
}
public PointInfo Scale(decimal scalingFactor) => new(this.X * scalingFactor, this.Y * scalingFactor);

View File

@@ -5,7 +5,7 @@
using System;
using System.Drawing;
namespace MouseJumpUI.Drawing.Models;
namespace MouseJumpUI.Models.Drawing;
/// <summary>
/// Immutable version of a System.Drawing.Rectangle object with some extra utility methods.
@@ -69,6 +69,14 @@ public sealed class RectangleInfo
public decimal Area => this.Width * this.Height;
/// <remarks>
/// Adapted from https://github.com/dotnet/runtime
/// See https://github.com/dotnet/runtime/blob/dfd618dc648ba9b11dd0f8034f78113d69f223cd/src/libraries/System.Drawing.Primitives/src/System/Drawing/Rectangle.cs
/// </remarks>
public bool Contains(RectangleInfo rect) =>
(this.X <= rect.X) && (rect.X + rect.Width <= this.X + this.Width) &&
(this.Y <= rect.Y) && (rect.Y + rect.Height <= this.Y + this.Height);
public RectangleInfo Enlarge(PaddingInfo padding) => new(
this.X + padding.Left,
this.Y + padding.Top,

View File

@@ -5,7 +5,7 @@
using System;
using System.Drawing;
namespace MouseJumpUI.Drawing.Models;
namespace MouseJumpUI.Models.Drawing;
/// <summary>
/// Immutable version of a System.Drawing.Size object with some extra utility methods.

View File

@@ -5,11 +5,11 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using MouseJumpUI.Models.Drawing;
using MouseJumpUI.Models.Screen;
namespace MouseJumpUI.Drawing.Models;
namespace MouseJumpUI.Models.Layout;
/// <summary>
/// Represents a collection of values needed for calculating the MainForm layout.
@@ -17,29 +17,31 @@ namespace MouseJumpUI.Drawing.Models;
public sealed class LayoutConfig
{
public LayoutConfig(
Rectangle virtualScreen,
IEnumerable<Rectangle> screenBounds,
Point activatedLocation,
int activatedScreen,
Size maximumFormSize,
Padding formPadding,
Padding previewPadding)
RectangleInfo virtualScreenBounds,
List<ScreenInfo> screens,
PointInfo activatedLocation,
int activatedScreenIndex,
int activatedScreenNumber,
SizeInfo maximumFormSize,
PaddingInfo formPadding,
PaddingInfo previewPadding)
{
// make sure the virtual screen entirely contains all of the individual screen bounds
ArgumentNullException.ThrowIfNull(screenBounds);
if (screenBounds.Any(screen => !virtualScreen.Contains(screen)))
ArgumentNullException.ThrowIfNull(virtualScreenBounds);
ArgumentNullException.ThrowIfNull(screens);
if (screens.Any(screen => !virtualScreenBounds.Contains(screen.Bounds)))
{
throw new ArgumentException($"'{nameof(virtualScreen)}' must contain all of the screens in '{nameof(screenBounds)}'", nameof(virtualScreen));
throw new ArgumentException($"'{nameof(virtualScreenBounds)}' must contain all of the screens in '{nameof(screens)}'", nameof(virtualScreenBounds));
}
this.VirtualScreen = new RectangleInfo(virtualScreen);
this.ScreenBounds = new(
screenBounds.Select(screen => new RectangleInfo(screen)).ToList());
this.ActivatedLocation = new(activatedLocation);
this.ActivatedScreen = activatedScreen;
this.MaximumFormSize = new(maximumFormSize);
this.FormPadding = new(formPadding);
this.PreviewPadding = new(previewPadding);
this.VirtualScreenBounds = virtualScreenBounds;
this.Screens = new(screens.ToList());
this.ActivatedLocation = activatedLocation;
this.ActivatedScreenIndex = activatedScreenIndex;
this.ActivatedScreenNumber = activatedScreenNumber;
this.MaximumFormSize = maximumFormSize;
this.FormPadding = formPadding;
this.PreviewPadding = previewPadding;
}
/// <summary>
@@ -49,15 +51,15 @@ public sealed class LayoutConfig
/// The Virtual Screen is the bounding rectangle of all the monitors.
/// https://learn.microsoft.com/en-us/windows/win32/gdi/the-virtual-screen
/// </remarks>
public RectangleInfo VirtualScreen
public RectangleInfo VirtualScreenBounds
{
get;
}
/// <summary>
/// Gets the bounds of all of the screens connected to the system.
/// Gets a collection containing the individual screens connected to the system.
/// </summary>
public ReadOnlyCollection<RectangleInfo> ScreenBounds
public ReadOnlyCollection<ScreenInfo> Screens
{
get;
}
@@ -67,8 +69,8 @@ public sealed class LayoutConfig
/// </summary>
/// <summary>
/// The preview form will be centered on this location unless there are any
/// constraints such as the being too close to edge of a screen, in which case
/// the form will be displayed as close as possible to this location.
/// constraints such as being too close to edge of a screen, in which case
/// the form will be displayed centered as close as possible to this location.
/// </summary>
public PointInfo ActivatedLocation
{
@@ -77,8 +79,19 @@ public sealed class LayoutConfig
/// <summary>
/// Gets the index of the screen the cursor was on when the form was activated.
/// The value is an index into the ScreenBounds array and is 0-indexed as a result.
/// </summary>
public int ActivatedScreen
public int ActivatedScreenIndex
{
get;
}
/// <summary>
/// Gets the screen number the cursor was on when the form was activated.
/// The value matches the screen numbering scheme in the "Display Settings" dialog
/// and is 1-indexed as a result.
/// </summary>
public int ActivatedScreenNumber
{
get;
}

View File

@@ -6,8 +6,9 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using MouseJumpUI.Models.Drawing;
namespace MouseJumpUI.Drawing.Models;
namespace MouseJumpUI.Models.Layout;
public sealed class LayoutInfo
{
@@ -42,7 +43,7 @@ public sealed class LayoutInfo
set;
}
public RectangleInfo? ActivatedScreen
public RectangleInfo? ActivatedScreenBounds
{
get;
set;
@@ -55,7 +56,7 @@ public sealed class LayoutInfo
formBounds: this.FormBounds ?? throw new InvalidOperationException(),
previewBounds: this.PreviewBounds ?? throw new InvalidOperationException(),
screenBounds: this.ScreenBounds ?? throw new InvalidOperationException(),
activatedScreen: this.ActivatedScreen ?? throw new InvalidOperationException());
activatedScreenBounds: this.ActivatedScreenBounds ?? throw new InvalidOperationException());
}
}
@@ -64,7 +65,7 @@ public sealed class LayoutInfo
RectangleInfo formBounds,
RectangleInfo previewBounds,
IEnumerable<RectangleInfo> screenBounds,
RectangleInfo activatedScreen)
RectangleInfo activatedScreenBounds)
{
this.LayoutConfig = layoutConfig ?? throw new ArgumentNullException(nameof(layoutConfig));
this.FormBounds = formBounds ?? throw new ArgumentNullException(nameof(formBounds));
@@ -72,7 +73,7 @@ public sealed class LayoutInfo
this.ScreenBounds = new(
(screenBounds ?? throw new ArgumentNullException(nameof(screenBounds)))
.ToList());
this.ActivatedScreen = activatedScreen ?? throw new ArgumentNullException(nameof(activatedScreen));
this.ActivatedScreenBounds = activatedScreenBounds ?? throw new ArgumentNullException(nameof(activatedScreenBounds));
}
/// <summary>
@@ -104,7 +105,7 @@ public sealed class LayoutInfo
get;
}
public RectangleInfo ActivatedScreen
public RectangleInfo ActivatedScreenBounds
{
get;
}

View File

@@ -0,0 +1,46 @@
// 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;
using MouseJumpUI.Models.Drawing;
namespace MouseJumpUI.Models.Screen;
/// <summary>
/// Immutable version of a System.Windows.Forms.Screen object so we don't need to
/// take a dependency on WinForms just for screen info.
/// </summary>
public sealed class ScreenInfo
{
internal ScreenInfo(int handle, bool primary, RectangleInfo displayArea, RectangleInfo workingArea)
{
this.Handle = handle;
this.Primary = primary;
this.DisplayArea = displayArea ?? throw new ArgumentNullException(nameof(displayArea));
this.WorkingArea = workingArea ?? throw new ArgumentNullException(nameof(workingArea));
}
public int Handle
{
get;
}
public bool Primary
{
get;
}
public RectangleInfo DisplayArea
{
get;
}
public RectangleInfo Bounds =>
this.DisplayArea;
public RectangleInfo WorkingArea
{
get;
}
}

View File

@@ -63,5 +63,6 @@
<ProjectReference Include="..\..\..\common\GPOWrapper\GPOWrapper.vcxproj" />
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup>
</Project>

View File

@@ -2,18 +2,20 @@
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
/// <summary>
/// A Boolean variable (should be TRUE or FALSE).
/// This type is declared in WinDef.h as follows:
/// typedef int BOOL;
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct BOOL
internal static partial class Core
{
/// <summary>
/// A Boolean variable (should be TRUE or FALSE).
/// This type is declared in WinDef.h as follows:
/// typedef int BOOL;
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct BOOL
{
public readonly int Value;
public BOOL(int value)
@@ -33,4 +35,10 @@ internal readonly struct BOOL
public static implicit operator int(BOOL value) => value.Value;
public static implicit operator BOOL(int value) => new(value);
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -0,0 +1,45 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
/// <summary>
/// The CRECT structure defines a rectangle by the coordinates of its upper-left and lower-right corners.
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/windef/ns-windef-rect
/// </remarks>
[SuppressMessage("Naming Rules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Name and value taken from Win32Api")]
internal readonly struct CRECT
{
public static readonly CRECT Empty = new(0, 0, 0, 0);
public readonly LONG left;
public readonly LONG top;
public readonly LONG right;
public readonly LONG bottom;
public CRECT(
LONG left, LONG top, LONG right, LONG bottom)
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
public static int Size =>
Marshal.SizeOf(typeof(CRECT));
public override string ToString()
{
return $"left={this.left},top={this.top},right={this.right},bottom={this.bottom}";
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
/// <summary>
/// A 32-bit unsigned integer. The range is 0 through 4294967295 decimal.
/// This type is declared in IntSafe.h as follows:
/// typedef unsigned long DWORD;
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct DWORD
{
public readonly uint Value;
public DWORD(uint value)
{
this.Value = value;
}
public static implicit operator uint(DWORD value) => value.Value;
public static implicit operator DWORD(uint value) => new(value);
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -0,0 +1,41 @@
// 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;
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
/// <summary>
/// A handle to an object.
/// This type is declared in WinNT.h as follows:
/// typedef PVOID HANDLE;
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct HANDLE
{
public static readonly HANDLE Null = new(IntPtr.Zero);
public readonly IntPtr Value;
public HANDLE(IntPtr value)
{
this.Value = value;
}
public bool IsNull => this.Value == HANDLE.Null.Value;
public static implicit operator IntPtr(HANDLE value) => value.Value;
public static implicit operator HANDLE(IntPtr value) => new(value);
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -4,18 +4,20 @@
using System;
namespace MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
/// <summary>
/// A handle to a device context (DC).
/// This type is declared in WinDef.h as follows:
/// typedef HANDLE HDC;
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct HDC
internal static partial class Core
{
/// <summary>
/// A handle to a device context (DC).
/// This type is declared in WinDef.h as follows:
/// typedef HANDLE HDC;
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct HDC
{
public static readonly HDC Null = new(IntPtr.Zero);
public readonly IntPtr Value;
@@ -26,4 +28,10 @@ internal readonly struct HDC
}
public bool IsNull => this.Value == HDC.Null.Value;
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -0,0 +1,49 @@
// 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;
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
/// <summary>
/// A handle to a display monitor.
/// This type is declared in WinDef.h as follows:
/// if(WINVER >= 0x0500) typedef HANDLE HMONITOR;
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct HMONITOR
{
public static readonly HMONITOR Null = new(IntPtr.Zero);
public readonly IntPtr Value;
public HMONITOR(IntPtr value)
{
this.Value = value;
}
public bool IsNull => this.Value == HMONITOR.Null.Value;
public static implicit operator int(HMONITOR value) => value.Value.ToInt32();
public static implicit operator HMONITOR(int value) => new(value);
public static implicit operator IntPtr(HMONITOR value) => value.Value;
public static implicit operator HMONITOR(IntPtr value) => new(value);
public static implicit operator HANDLE(HMONITOR value) => new(value.Value);
public static implicit operator HMONITOR(HANDLE value) => new(value.Value);
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -4,18 +4,20 @@
using System;
namespace MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
/// <summary>
/// A handle to a window.
/// This type is declared in WinDef.h as follows:
/// typedef HANDLE HWND;
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct HWND
internal static partial class Core
{
/// <summary>
/// A handle to a window.
/// This type is declared in WinDef.h as follows:
/// typedef HANDLE HWND;
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct HWND
{
public static readonly HWND Null = new(IntPtr.Zero);
public readonly IntPtr Value;
@@ -26,4 +28,10 @@ internal readonly struct HWND
}
public bool IsNull => this.Value == HWND.Null.Value;
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
/// <summary>
/// A 32-bit signed integer.The range is -2147483648 through 2147483647 decimal.
/// This type is declared in WinNT.h as follows:
/// typedef long LONG;
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct LONG
{
public readonly int Value;
public LONG(int value)
{
this.Value = value;
}
public static implicit operator int(LONG value) => value.Value;
public static implicit operator LONG(int value) => new(value);
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -0,0 +1,38 @@
// 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;
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
/// <summary>
/// A message parameter.
/// This type is declared in WinDef.h as follows:
/// typedef LONG_PTR LPARAM;
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct LPARAM
{
public static readonly LPARAM Null = new(IntPtr.Zero);
public readonly IntPtr Value;
public LPARAM(IntPtr value)
{
this.Value = value;
}
public static implicit operator IntPtr(LPARAM value) => value.Value;
public static implicit operator LPARAM(IntPtr value) => new(value);
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -0,0 +1,49 @@
// 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;
using System.Runtime.InteropServices;
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
internal readonly struct LPCRECT
{
public static readonly LPCRECT Null = new(IntPtr.Zero);
public readonly IntPtr Value;
public LPCRECT(IntPtr value)
{
this.Value = value;
}
public LPCRECT(CRECT value)
{
this.Value = LPCRECT.ToPtr(value);
}
private static IntPtr ToPtr(CRECT value)
{
var ptr = Marshal.AllocHGlobal(CRECT.Size);
Marshal.StructureToPtr(value, ptr, false);
return ptr;
}
public void Free()
{
Marshal.FreeHGlobal(this.Value);
}
public static implicit operator IntPtr(LPCRECT value) => value.Value;
public static implicit operator LPCRECT(IntPtr value) => new(value);
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -0,0 +1,54 @@
// 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;
using System.Runtime.InteropServices;
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
internal readonly struct LPPOINT
{
public static readonly LPPOINT Null = new(IntPtr.Zero);
public readonly IntPtr Value;
public LPPOINT(IntPtr value)
{
this.Value = value;
}
public LPPOINT(POINT value)
{
this.Value = LPPOINT.ToPtr(value);
}
private static IntPtr ToPtr(POINT value)
{
var ptr = Marshal.AllocHGlobal(POINT.Size);
Marshal.StructureToPtr(value, ptr, false);
return ptr;
}
public POINT ToStructure()
{
return Marshal.PtrToStructure<POINT>(this.Value);
}
public void Free()
{
Marshal.FreeHGlobal(this.Value);
}
public static implicit operator IntPtr(LPPOINT value) => value.Value;
public static implicit operator LPPOINT(IntPtr value) => new(value);
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -0,0 +1,48 @@
// 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;
using System.Runtime.InteropServices;
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
internal readonly struct LPRECT
{
public static readonly LPRECT Null = new(IntPtr.Zero);
public readonly IntPtr Value;
public LPRECT(IntPtr value)
{
this.Value = value;
}
public LPRECT(RECT value)
{
this.Value = LPRECT.ToPtr(value);
}
private static IntPtr ToPtr(RECT value)
{
var ptr = Marshal.AllocHGlobal(RECT.Size);
Marshal.StructureToPtr(value, ptr, false);
return ptr;
}
public void Free()
{
Marshal.FreeHGlobal(this.Value);
}
public static implicit operator IntPtr(LPRECT value) => value.Value;
public static implicit operator LPRECT(IntPtr value) => new(value);
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -0,0 +1,47 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
/// <summary>
/// The POINT structure defines the x- and y-coordinates of a point.
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/windef/ns-windef-point
/// </remarks>
[SuppressMessage("SA1307", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Names match Win32 api")]
internal readonly struct POINT
{
/// <summary>
/// Specifies the x-coordinate of the point.
/// </summary>
public readonly LONG x;
/// <summary>
/// Specifies the y-coordinate of the point.
/// </summary>
public readonly LONG y;
public POINT(
LONG x,
LONG y)
{
this.x = x;
this.y = y;
}
public static int Size =>
Marshal.SizeOf(typeof(POINT));
public override string ToString()
{
return $"x={this.x},y={this.y}";
}
}
}

View File

@@ -0,0 +1,45 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
/// <summary>
/// The RECT structure defines a rectangle by the coordinates of its upper-left and lower-right corners.
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/windef/ns-windef-rect
/// </remarks>
[SuppressMessage("Naming Rules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Name and value taken from Win32Api")]
internal readonly struct RECT
{
public static readonly RECT Empty = new(0, 0, 0, 0);
public readonly LONG left;
public readonly LONG top;
public readonly LONG right;
public readonly LONG bottom;
public RECT(
LONG left, LONG top, LONG right, LONG bottom)
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
public static int Size =>
Marshal.SizeOf(typeof(RECT));
public override string ToString()
{
return $"left={this.left},top={this.top},right={this.right},bottom={this.bottom}";
}
}
}

View File

@@ -0,0 +1,34 @@
// 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.
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
/// <summary>
/// An unsigned INT. The range is 0 through 4294967295 decimal.
/// This type is declared in WinDef.h as follows:
/// typedef unsigned int UINT;
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct UINT
{
public readonly uint Value;
public UINT(uint value)
{
this.Value = value;
}
public static implicit operator uint(UINT value) => value.Value;
public static implicit operator UINT(uint value) => new(value);
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -0,0 +1,44 @@
// 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;
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
/// <summary>
/// An unsigned LONG_PTR.
/// This type is declared in BaseTsd.h as follows:
/// C++
/// #if defined(_WIN64)
/// typedef unsigned __int64 ULONG_PTR;
/// #else
/// typedef unsigned long ULONG_PTR;
/// #endif
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct ULONG_PTR
{
public static readonly ULONG_PTR Null = new(UIntPtr.Zero);
public readonly UIntPtr Value;
public ULONG_PTR(UIntPtr value)
{
this.Value = value;
}
public static implicit operator UIntPtr(ULONG_PTR value) => value.Value;
public static implicit operator ULONG_PTR(UIntPtr value) => new(value);
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -0,0 +1,35 @@
// 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.
namespace MouseJumpUI.NativeMethods;
internal static partial class Core
{
/// <summary>
/// A 16-bit unsigned integer.The range is 0 through 65535 decimal.
/// This type is declared in WinDef.h as follows:
/// typedef unsigned short WORD;
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct WORD
{
public readonly ushort Value;
public WORD(ushort value)
{
this.Value = value;
}
public static implicit operator ulong(WORD value) => value.Value;
public static implicit operator WORD(ushort value) => new(value);
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -14,7 +14,7 @@ internal static partial class Gdi32
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-bitblt
/// </remarks>
public enum ROP_CODE : uint
internal enum ROP_CODE : uint
{
BLACKNESS = 0x00000042,
CAPTUREBLT = 0x40000000,

View File

@@ -12,7 +12,7 @@ internal static partial class Gdi32
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-setstretchbltmode
/// </remarks>
public enum STRETCH_BLT_MODE : int
internal enum STRETCH_BLT_MODE : int
{
BLACKONWHITE = 1,
COLORONCOLOR = 3,

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices;
using MouseJumpUI.NativeMethods.Core;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
@@ -21,7 +21,7 @@ internal static partial class Gdi32
/// See https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-setstretchbltmode
/// </remarks>
[LibraryImport(Libraries.Gdi32)]
public static partial int SetStretchBltMode(
internal static partial int SetStretchBltMode(
HDC hdc,
STRETCH_BLT_MODE mode);
}

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices;
using MouseJumpUI.NativeMethods.Core;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
@@ -23,7 +23,7 @@ internal static partial class Gdi32
/// See https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-stretchblt
/// </remarks>
[LibraryImport(Libraries.Gdi32)]
public static partial BOOL StretchBlt(
internal static partial BOOL StretchBlt(
HDC hdcDest,
int xDest,
int yDest,

View File

@@ -0,0 +1,33 @@
// 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.Runtime.InteropServices;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <summary>
/// The EnumDisplayMonitors function enumerates display monitors (including invisible
/// pseudo-monitors associated with the mirroring drivers) that intersect a region formed
/// by the intersection of a specified clipping rectangle and the visible region of a
/// device context. EnumDisplayMonitors calls an application-defined MonitorEnumProc
/// callback function once for each monitor that is enumerated. Note that
/// GetSystemMetrics (SM_CMONITORS) counts only the display monitors.
/// </summary>
/// <returns>
/// If the function succeeds, the return value is nonzero.
/// If the function fails, the return value is zero.
/// </returns>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
/// </remarks>
[LibraryImport(Libraries.User32)]
internal static partial BOOL EnumDisplayMonitors(
HDC hdc,
LPCRECT lprcClip,
MONITORENUMPROC lpfnEnum,
LPARAM dwData);
}

View File

@@ -0,0 +1,26 @@
// 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.Runtime.InteropServices;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <summary>
/// The GetMonitorInfo function retrieves information about a display monitor.
/// </summary>
/// <returns>
/// If the function succeeds, the return value is nonzero.
/// If the function fails, the return value is zero.
/// </returns>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
/// </remarks>
[LibraryImport(Libraries.User32)]
internal static partial BOOL GetMonitorInfoW(
HMONITOR hMonitor,
LPMONITORINFO lpmi);
}

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices;
using MouseJumpUI.NativeMethods.Core;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
@@ -26,6 +26,6 @@ internal static partial class User32
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowdc
/// </remarks>
[LibraryImport(Libraries.User32)]
public static partial HDC GetWindowDC(
internal static partial HDC GetWindowDC(
HWND hWnd);
}

View File

@@ -0,0 +1,53 @@
// 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;
using System.Runtime.InteropServices;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput
/// </remarks>
internal readonly struct LPMONITORINFO
{
public static readonly LPMONITORINFO Null = new(IntPtr.Zero);
public readonly IntPtr Value;
public LPMONITORINFO(IntPtr value)
{
this.Value = value;
}
public LPMONITORINFO(MONITORINFO value)
{
this.Value = LPMONITORINFO.ToPtr(value);
}
public MONITORINFO ToStructure()
{
return Marshal.PtrToStructure<MONITORINFO>(this.Value);
}
private static IntPtr ToPtr(MONITORINFO value)
{
var ptr = Marshal.AllocHGlobal(MONITORINFO.Size);
Marshal.StructureToPtr(value, ptr, false);
return ptr;
}
public void Free()
{
Marshal.FreeHGlobal(this.Value);
}
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -0,0 +1,22 @@
// 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 static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <summary>
/// A MonitorEnumProc function is an application-defined callback function that is called by the EnumDisplayMonitors function.
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-monitorenumproc
/// </remarks>
internal delegate BOOL MONITORENUMPROC(
HMONITOR unnamedParam1,
HDC unnamedParam2,
LPRECT unnamedParam3,
LPARAM unnamedParam4);
}

View File

@@ -0,0 +1,39 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <summary>
/// Used by SendInput to store information for synthesizing input events such as keystrokes, mouse movement, and mouse clicks.
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfo
/// </remarks>
[SuppressMessage("SA1307", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Parameter name matches Win32 api")]
[StructLayout(LayoutKind.Sequential)]
internal readonly struct MONITORINFO
{
public readonly DWORD cbSize;
public readonly RECT rcMonitor;
public readonly RECT rcWork;
public readonly MONITOR_INFO_FLAGS dwFlags;
public MONITORINFO(DWORD cbSize, RECT rcMonitor, RECT rcWork, MONITOR_INFO_FLAGS dwFlags)
{
this.cbSize = cbSize;
this.rcMonitor = rcMonitor;
this.rcWork = rcWork;
this.dwFlags = dwFlags;
}
public static int Size =>
Marshal.SizeOf(typeof(INPUT));
}
}

View File

@@ -0,0 +1,24 @@
// 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.Diagnostics.CodeAnalysis;
namespace MouseJumpUI.NativeMethods;
[SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")]
internal static partial class User32
{
/// <summary>
/// Determines the function's return value if the point is not contained within any display monitor.
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfrompoint
/// </remarks>
internal enum MONITOR_FROM_FLAGS : uint
{
MONITOR_DEFAULTTONULL = 0x00000000,
MONITOR_DEFAULTTOPRIMARY = 0x00000001,
MONITOR_DEFAULTTONEAREST = 0x00000002,
}
}

View File

@@ -0,0 +1,22 @@
// 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.Diagnostics.CodeAnalysis;
namespace MouseJumpUI.NativeMethods;
[SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")]
internal static partial class User32
{
/// <summary>
/// A set of flags that represent attributes of the display monitor.
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfo
/// </remarks>
internal enum MONITOR_INFO_FLAGS : uint
{
MONITORINFOF_PRIMARY = 1,
}
}

View File

@@ -0,0 +1,26 @@
// 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.Runtime.InteropServices;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <summary>
/// The MonitorFromPoint function retrieves a handle to the display monitor that contains a specified point.
/// </summary>
/// <returns>
/// If the point is contained by a display monitor, the return value is an HMONITOR handle to that display monitor.
/// If the point is not contained by a display monitor, the return value depends on the value of dwFlags.
/// </returns>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfrompoint
/// </remarks>
[LibraryImport(Libraries.User32)]
internal static partial HMONITOR MonitorFromPoint(
POINT pt,
MONITOR_FROM_FLAGS dwFlags);
}

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices;
using MouseJumpUI.NativeMethods.Core;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
@@ -22,7 +22,7 @@ internal static partial class User32
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-releasedc
/// </remarks>
[LibraryImport(Libraries.User32)]
public static partial int ReleaseDC(
internal static partial int ReleaseDC(
HWND hWnd,
HDC hDC);
}

View File

@@ -0,0 +1,38 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <summary>
/// Contains information about a simulated message generated by an input device
/// other than a keyboard or mouse.
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-hardwareinput
/// </remarks>
[SuppressMessage("SA1307", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Parameter name matches Win32 api")]
[StructLayout(LayoutKind.Sequential)]
internal readonly struct HARDWAREINPUT
{
public readonly DWORD uMsg;
public readonly WORD wParamL;
public readonly WORD wParamH;
public HARDWAREINPUT(
DWORD uMsg,
WORD wParamL,
WORD wParamH)
{
this.uMsg = uMsg;
this.wParamL = wParamL;
this.wParamH = wParamH;
}
}
}

View File

@@ -0,0 +1,51 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <summary>
/// Used by SendInput to store information for synthesizing input events such as keystrokes, mouse movement, and mouse clicks.
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-input
/// </remarks>
[SuppressMessage("SA1307", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Parameter name matches Win32 api")]
[StructLayout(LayoutKind.Sequential)]
internal readonly struct INPUT
{
public readonly INPUT_TYPE type;
public readonly DUMMYUNIONNAME data;
public INPUT(INPUT_TYPE type, DUMMYUNIONNAME data)
{
this.type = type;
this.data = data;
}
public static int Size =>
Marshal.SizeOf(typeof(INPUT));
[StructLayout(LayoutKind.Explicit)]
public readonly struct DUMMYUNIONNAME
{
[FieldOffset(0)]
public readonly MOUSEINPUT mi;
[FieldOffset(0)]
public readonly KEYBDINPUT ki;
[FieldOffset(0)]
public readonly HARDWAREINPUT hi;
public DUMMYUNIONNAME(MOUSEINPUT mi)
{
this.mi = mi;
}
}
}
}

View File

@@ -0,0 +1,24 @@
// 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.Diagnostics.CodeAnalysis;
namespace MouseJumpUI.NativeMethods;
[SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")]
internal static partial class User32
{
/// <summary>
/// The type of the input event.
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-input
/// </remarks>
internal enum INPUT_TYPE : uint
{
INPUT_MOUSE = 0,
INPUT_KEYBOARD = 1,
INPUT_HARDWARE = 2,
}
}

View File

@@ -0,0 +1,43 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <summary>
/// Contains information about a simulated keyboard event.
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-keybdinput
/// </remarks>
[SuppressMessage("SA1307", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Parameter name matches Win32 api")]
[StructLayout(LayoutKind.Sequential)]
internal readonly struct KEYBDINPUT
{
public readonly WORD wVk;
public readonly WORD wScan;
public readonly DWORD dwFlags;
public readonly DWORD time;
public readonly ULONG_PTR dwExtraInfo;
public KEYBDINPUT(
WORD wVk,
WORD wScan,
DWORD dwFlags,
DWORD time,
ULONG_PTR dwExtraInfo)
{
this.wVk = wVk;
this.wScan = wScan;
this.dwFlags = dwFlags;
this.time = time;
this.dwExtraInfo = dwExtraInfo;
}
}
}

View File

@@ -0,0 +1,72 @@
// 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;
using System.Collections.Generic;
using System.Runtime.InteropServices;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput
/// </remarks>
internal readonly struct LPINPUT
{
public static readonly LPINPUT Null = new(IntPtr.Zero);
public readonly IntPtr Value;
public LPINPUT(IntPtr value)
{
this.Value = value;
}
public LPINPUT(INPUT[] values)
{
this.Value = LPINPUT.ToPtr(values);
}
public INPUT ToStructure()
{
return Marshal.PtrToStructure<INPUT>(this.Value);
}
public IEnumerable<INPUT> ToStructure(int count)
{
var ptr = this.Value;
var size = INPUT.Size;
for (var i = 0; i < count; i++)
{
yield return Marshal.PtrToStructure<INPUT>(this.Value);
ptr += size;
}
}
private static IntPtr ToPtr(INPUT[] values)
{
var mem = Marshal.AllocHGlobal(INPUT.Size * values.Length);
var ptr = mem;
var size = INPUT.Size;
foreach (var value in values)
{
Marshal.StructureToPtr(value, ptr, true);
ptr += size;
}
return mem;
}
public void Free()
{
Marshal.FreeHGlobal(this.Value);
}
public override string ToString()
{
return $"{this.GetType().Name}({this.Value})";
}
}
}

View File

@@ -0,0 +1,46 @@
// 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.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <summary>
/// Contains information about a simulated mouse event.
/// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput
/// </remarks>
[SuppressMessage("SA1307", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Parameter name matches Win32 api")]
[StructLayout(LayoutKind.Sequential)]
internal readonly struct MOUSEINPUT
{
public readonly int dx;
public readonly int dy;
public readonly DWORD mouseData;
public readonly MOUSE_EVENT_FLAGS dwFlags;
public readonly DWORD time;
public readonly ULONG_PTR dwExtraInfo;
public MOUSEINPUT(
int dx,
int dy,
DWORD mouseData,
MOUSE_EVENT_FLAGS dwFlags,
DWORD time,
ULONG_PTR dwExtraInfo)
{
this.dx = dx;
this.dy = dy;
this.mouseData = mouseData;
this.dwFlags = dwFlags;
this.time = time;
this.dwExtraInfo = dwExtraInfo;
}
}
}

View File

@@ -0,0 +1,34 @@
// 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;
using System.Diagnostics.CodeAnalysis;
namespace MouseJumpUI.NativeMethods;
[SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")]
internal static partial class User32
{
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-mouseinput
/// </remarks>
[Flags]
internal enum MOUSE_EVENT_FLAGS : uint
{
MOUSEEVENTF_MOVE = 0x0001,
MOUSEEVENTF_LEFTDOWN = 0x0002,
MOUSEEVENTF_LEFTUP = 0x0004,
MOUSEEVENTF_RIGHTDOWN = 0x0008,
MOUSEEVENTF_RIGHTUP = 0x0010,
MOUSEEVENTF_MIDDLEDOWN = 0x0020,
MOUSEEVENTF_MIDDLEUP = 0x0040,
MOUSEEVENTF_XDOWN = 0x0080,
MOUSEEVENTF_XUP = 0x0100,
MOUSEEVENTF_WHEEL = 0x0800,
MOUSEEVENTF_HWHEEL = 0x1000,
MOUSEEVENTF_MOVE_NOCOALESCE = 0x2000,
MOUSEEVENTF_VIRTUALDESK = 0x4000,
MOUSEEVENTF_ABSOLUTE = 0x8000,
}
}

View File

@@ -0,0 +1,28 @@
// 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.Runtime.InteropServices;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <summary>
/// Synthesizes keystrokes, mouse motions, and button clicks.
/// </summary>
/// <returns>
/// The function returns the number of events that it successfully inserted into the keyboard or mouse input stream.
/// If the function returns zero, the input was already blocked by another thread.
/// To get extended error information, call GetLastError.
/// </returns>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput
/// </remarks>
[LibraryImport(Libraries.User32, SetLastError = true)]
internal static partial UINT SendInput(
UINT cInputs,
LPINPUT pInputs,
int cbSize);
}

View File

@@ -0,0 +1,25 @@
// 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.Runtime.InteropServices;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <summary>
/// Retrieves the position of the mouse cursor, in screen coordinates.
/// </summary>
/// <returns>
/// Returns nonzero if successful or zero otherwise.
/// To get extended error information, call GetLastError.
/// </returns>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getcursorpos
/// </remarks>
[LibraryImport(Libraries.User32, SetLastError = true)]
internal static partial BOOL GetCursorPos(
LPPOINT lpPoint);
}

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices;
using MouseJumpUI.NativeMethods.Core;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
@@ -20,5 +20,5 @@ internal static partial class User32
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdesktopwindow
/// </remarks>
[LibraryImport(Libraries.User32)]
public static partial HWND GetDesktopWindow();
internal static partial HWND GetDesktopWindow();
}

View File

@@ -0,0 +1,26 @@
// 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.Runtime.InteropServices;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <summary>
/// Retrieves the specified system metric or system configuration setting.
///
/// Note that all dimensions retrieved by GetSystemMetrics are in pixels.
/// </summary>
/// <returns>
/// If the function succeeds, the return value is the requested system metric or configuration setting.
/// If the function fails, the return value is 0. GetLastError does not provide extended error information.
/// </returns>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getsystemmetrics
/// </remarks>
[LibraryImport(Libraries.User32)]
internal static partial int GetSystemMetrics(
SYSTEM_METRICS_INDEX smIndex);
}

View File

@@ -0,0 +1,110 @@
// 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.Diagnostics.CodeAnalysis;
namespace MouseJumpUI.NativeMethods;
[SuppressMessage("SA1310", "SA1310:FieldNamesMustNotContainUnderscore", Justification = "Names match Win32 api")]
internal static partial class User32
{
internal enum SYSTEM_METRICS_INDEX : uint
{
SM_ARRANGE = 56,
SM_CLEANBOOT = 67,
SM_CMONITORS = 80,
SM_CMOUSEBUTTONS = 43,
SM_CONVERTIBLESLATEMODE = 0x2003,
SM_CXBORDER = 5,
SM_CXCURSOR = 13,
SM_CXDLGFRAME = 7,
SM_CXDOUBLECLK = 36,
SM_CXDRAG = 68,
SM_CXEDGE = 45,
SM_CXFIXEDFRAME = SM_CXDLGFRAME,
SM_CXFOCUSBORDER = 83,
SM_CXFRAME = 32,
SM_CXFULLSCREEN = 16,
SM_CXHSCROLL = 21,
SM_CXHTHUMB = 10,
SM_CXICON = 11,
SM_CXICONSPACING = 38,
SM_CXMAXIMIZED = 61,
SM_CXMAXTRACK = 59,
SM_CXMENUCHECK = 71,
SM_CXMENUSIZE = 54,
SM_CXMIN = 28,
SM_CXMINIMIZED = 57,
SM_CXMINSPACING = 47,
SM_CXMINTRACK = 34,
SM_CXPADDEDBORDER = 92,
SM_CXSCREEN = 0,
SM_CXSIZE = 30,
SM_CXSIZEFRAME = SM_CXFRAME,
SM_CXSMICON = 49,
SM_CXSMSIZE = 52,
SM_CXVIRTUALSCREEN = 78,
SM_CXVSCROLL = 2,
SM_CYBORDER = 6,
SM_CYCAPTION = 4,
SM_CYCURSOR = 14,
SM_CYDLGFRAME = 8,
SM_CYDOUBLECLK = 37,
SM_CYDRAG = 69,
SM_CYEDGE = 46,
SM_CYFIXEDFRAME = SM_CYDLGFRAME,
SM_CYFOCUSBORDER = 84,
SM_CYFRAME = 33,
SM_CYFULLSCREEN = 17,
SM_CYHSCROLL = 3,
SM_CYICON = 12,
SM_CYICONSPACING = 39,
SM_CYKANJIWINDOW = 18,
SM_CYMAXIMIZED = 62,
SM_CYMAXTRACK = 60,
SM_CYMENU = 15,
SM_CYMENUCHECK = 72,
SM_CYMENUSIZE = 55,
SM_CYMIN = 29,
SM_CYMINIMIZED = 58,
SM_CYMINSPACING = 48,
SM_CYMINTRACK = 35,
SM_CYSCREEN = 1,
SM_CYSIZE = 31,
SM_CYSIZEFRAME = SM_CYFRAME,
SM_CYSMCAPTION = 51,
SM_CYSMICON = 50,
SM_CYSMSIZE = 53,
SM_CYVIRTUALSCREEN = 79,
SM_CYVSCROLL = 20,
SM_CYVTHUMB = 9,
SM_DBCSENABLED = 42,
SM_DEBUG = 22,
SM_DIGITIZER = 94,
SM_IMMENABLED = 82,
SM_MAXIMUMTOUCHES = 95,
SM_MEDIACENTER = 87,
SM_MENUDROPALIGNMENT = 40,
SM_MIDEASTENABLED = 74,
SM_MOUSEPRESENT = 19,
SM_MOUSEHORIZONTALWHEELPRESENT = 91,
SM_MOUSEWHEELPRESENT = 75,
SM_NETWORK = 63,
SM_PENWINDOWS = 41,
SM_REMOTECONTROL = 0x2001,
SM_REMOTESESSION = 0x1000,
SM_SAMEDISPLAYFORMA = 81,
SM_SECURE = 44,
SM_SERVERR2 = 89,
SM_SHOWSOUNDS = 70,
SM_SHUTTINGDOWN = 0x2000,
SM_SLOWMACHINE = 73,
SM_STARTER = 88,
SM_SWAPBUTTON = 23,
SM_SYSTEMDOCKED = 0x2004,
SM_TABLETPC = 86,
SM_XVIRTUALSCREEN = 76,
SM_YVIRTUALSCREEN = 77,
}
}

View File

@@ -0,0 +1,28 @@
// 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.Runtime.InteropServices;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods;
internal static partial class User32
{
/// <summary>
/// Moves the cursor to the specified screen coordinates. If the new coordinates are not within
/// the screen rectangle set by the most recent ClipCursor function call, the system automatically
/// adjusts the coordinates so that the cursor stays within the rectangle.
/// </summary>
/// <returns>
/// Returns nonzero if successful or zero otherwise.
/// To get extended error information, call GetLastError.
/// </returns>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getcursorpos
/// </remarks>
[LibraryImport(Libraries.User32, SetLastError = true)]
internal static partial BOOL SetCursorPos(
int X,
int Y);
}

View File

@@ -1,21 +0,0 @@
// 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;
using MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeWrappers;
internal static partial class Gdi32
{
public static int SetStretchBltMode(HDC hdc, NativeMethods.Gdi32.STRETCH_BLT_MODE mode)
{
var result = NativeMethods.Gdi32.SetStretchBltMode(hdc, mode);
return result == 0
? throw new InvalidOperationException(
$"{nameof(Gdi32.SetStretchBltMode)} returned {result}")
: result;
}
}

View File

@@ -1,43 +0,0 @@
// 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;
using MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeWrappers;
internal static partial class Gdi32
{
public static BOOL StretchBlt(
HDC hdcDest,
int xDest,
int yDest,
int wDest,
int hDest,
HDC hdcSrc,
int xSrc,
int ySrc,
int wSrc,
int hSrc,
NativeMethods.Gdi32.ROP_CODE rop)
{
var result = NativeMethods.Gdi32.StretchBlt(
hdcDest,
xDest,
yDest,
wDest,
hDest,
hdcSrc,
xSrc,
ySrc,
wSrc,
hSrc,
rop);
return result
? result
: throw new InvalidOperationException(
$"{nameof(Gdi32.StretchBlt)} returned {result.Value}");
}
}

View File

@@ -1,16 +0,0 @@
// 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;
using MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeWrappers;
internal static partial class User32
{
public static HWND GetDesktopWindow()
{
return NativeMethods.User32.GetDesktopWindow();
}
}

View File

@@ -1,21 +0,0 @@
// 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;
using MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeWrappers;
internal static partial class User32
{
public static HDC GetWindowDC(HWND hWnd)
{
var hdc = NativeMethods.User32.GetWindowDC(hWnd);
return hdc.IsNull
? throw new InvalidOperationException(
$"{nameof(User32.GetWindowDC)} returned null")
: hdc;
}
}

View File

@@ -1,21 +0,0 @@
// 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;
using MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeWrappers;
internal static partial class User32
{
public static int ReleaseDC(HWND hWnd, HDC hDC)
{
var result = NativeMethods.User32.ReleaseDC(hWnd, hDC);
return result == 0
? throw new InvalidOperationException(
$"{nameof(User32.ReleaseDC)} returned {result}")
: result;
}
}

View File

@@ -3,8 +3,11 @@
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.Text.Json;
using System.Windows.Forms;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
namespace MouseJumpUI;
@@ -35,6 +38,34 @@ internal static class Program
return;
}
Application.Run(new MainForm());
var settings = Program.ReadSettings();
var mainForm = new MainForm(settings);
Application.Run(mainForm);
}
private static MouseJumpSettings ReadSettings()
{
var settingsUtils = new SettingsUtils();
var settingsPath = settingsUtils.GetSettingsFilePath(MouseJumpSettings.ModuleName);
if (!File.Exists(settingsPath))
{
var scaffoldSettings = new MouseJumpSettings();
settingsUtils.SaveSettings(JsonSerializer.Serialize(scaffoldSettings), MouseJumpSettings.ModuleName);
}
var settings = new MouseJumpSettings();
try
{
settings = settingsUtils.GetSettings<MouseJumpSettings>(MouseJumpSettings.ModuleName);
}
catch (Exception ex)
{
var errorMessage = $"There was a problem reading the configuration file. Error: {ex.GetType()} {ex.Message}";
Logger.LogInfo(errorMessage);
Logger.LogDebug(errorMessage);
}
return settings;
}
}

View File

@@ -11,9 +11,13 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("activation_shortcut")]
public HotkeySettings ActivationShortcut { get; set; }
[JsonPropertyName("thumbnail_size")]
public MouseJumpThumbnailSize ThumbnailSize { get; set; }
public MouseJumpProperties()
{
ActivationShortcut = new HotkeySettings(true, false, false, true, 0x44);
ThumbnailSize = new MouseJumpThumbnailSize();
}
}
}

View File

@@ -0,0 +1,68 @@
// 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;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;
namespace Microsoft.PowerToys.Settings.UI.Library
{
public class MouseJumpThumbnailSize : INotifyPropertyChanged
{
private int _width;
private int _height;
[JsonPropertyName("width")]
public int Width
{
get
{
return _width;
}
set
{
var newWidth = Math.Max(0, value);
if (newWidth != _width)
{
_width = newWidth;
OnPropertyChanged();
}
}
}
[JsonPropertyName("height")]
public int Height
{
get
{
return _height;
}
set
{
var newHeight = Math.Max(0, value);
if (newHeight != _height)
{
_height = newHeight;
OnPropertyChanged();
}
}
}
public MouseJumpThumbnailSize()
{
Width = 1600;
Height = 1200;
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}

View File

@@ -3158,4 +3158,21 @@ Activate by holding the key for the character you want to add an accent to, then
<data name="PastePlain_ShortcutWarning.Title" xml:space="preserve">
<value>Using this shortcut may prevent non-text paste actions (e.g. images, files) or built-in paste plain text actions in other applications from functioning.</value>
</data>
<data name="MouseUtils_MouseJump_ThumbnailSize.Header" xml:space="preserve">
<value>Thumbnail Size</value>
</data>
<data name="MouseUtils_MouseJump_ThumbnailSize_Description_Prefix.Text" xml:space="preserve">
<value>Constrain thumbnail image size to a maximum of</value>
</data>
<data name="MouseUtils_MouseJump_ThumbnailSize_Description_Suffix.Text" xml:space="preserve">
<value>pixels</value>
</data>
<data name="MouseUtils_MouseJump_ThumbnailSize_Edit_Height.Header" xml:space="preserve">
<value>Maximum height (px)</value>
<comment>px = pixels</comment>
</data>
<data name="MouseUtils_MouseJump_ThumbnailSize_Edit_Width.Header" xml:space="preserve">
<value>Maximum width (px)</value>
<comment>px = pixels</comment>
</data>
</root>

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Library;
@@ -86,6 +87,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
MouseJumpSettingsConfig = mouseJumpSettingsRepository.SettingsConfig;
MouseJumpSettingsConfig.Properties.ThumbnailSize.PropertyChanged += MouseJumpThumbnailSizePropertyChanged;
if (mousePointerCrosshairsSettingsRepository == null)
{
@@ -599,6 +601,29 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
public MouseJumpThumbnailSize MouseJumpThumbnailSize
{
get
{
return MouseJumpSettingsConfig.Properties.ThumbnailSize;
}
set
{
if ((MouseJumpSettingsConfig.Properties.ThumbnailSize.Width != value?.Width)
&& (MouseJumpSettingsConfig.Properties.ThumbnailSize.Height != value?.Height))
{
MouseJumpSettingsConfig.Properties.ThumbnailSize = value;
NotifyMouseJumpPropertyChanged();
}
}
}
public void MouseJumpThumbnailSizePropertyChanged(object sender, PropertyChangedEventArgs e)
{
NotifyMouseJumpPropertyChanged(nameof(MouseJumpThumbnailSize));
}
public void NotifyMouseJumpPropertyChanged([CallerMemberName] string propertyName = null)
{
OnPropertyChanged(propertyName);

View File

@@ -229,6 +229,81 @@
MinWidth="{StaticResource SettingActionControlMinWidth}"
HotkeySettings="{x:Bind Path=ViewModel.MouseJumpActivationShortcut, Mode=TwoWay}" />
</labs:SettingsCard>
<labs:SettingsCard
x:Uid="MouseUtils_MouseJump_ThumbnailSize"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource SymbolThemeFontFamily}, Glyph=&#xE740;}"
IsEnabled="{x:Bind ViewModel.IsMouseJumpEnabled, Mode=OneWay}">
<labs:SettingsCard.Description>
<StackPanel
Grid.Row="1"
Grid.Column="1"
Margin="0,4,0,0"
Orientation="Horizontal">
<TextBlock
x:Uid="MouseUtils_MouseJump_ThumbnailSize_Description_Prefix"
Margin="0,0,4,0"
Style="{ThemeResource SecondaryTextStyle}" />
<TextBlock
Margin="0,0,4,0"
FontWeight="SemiBold"
Style="{ThemeResource SecondaryTextStyle}"
Text="{x:Bind ViewModel.MouseJumpThumbnailSize.Width, Mode=OneWay}" />
<TextBlock
Margin="0,5,4,0"
AutomationProperties.AccessibilityView="Raw"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="10"
Foreground="{ThemeResource SystemBaseMediumColor}"
Style="{ThemeResource SecondaryTextStyle}"
Text="&#xE947;" />
<TextBlock
Margin="0,0,4,0"
FontWeight="SemiBold"
Style="{ThemeResource SecondaryTextStyle}"
Text="{x:Bind ViewModel.MouseJumpThumbnailSize.Height, Mode=OneWay}" />
<TextBlock
x:Uid="MouseUtils_MouseJump_ThumbnailSize_Description_Suffix"
Margin="0,0,4,0"
Foreground="{ThemeResource SystemBaseMediumColor}"
Style="{ThemeResource SecondaryTextStyle}" />
</StackPanel>
</labs:SettingsCard.Description>
<StackPanel
Grid.Column="2"
HorizontalAlignment="Right"
Orientation="Horizontal"
Spacing="8">
<Button
x:Uid="EditButton"
Width="40"
Height="36"
Content="&#xE70F;"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
Style="{StaticResource SubtleButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="EditTooltip" />
</ToolTipService.ToolTip>
<Button.Flyout>
<Flyout x:Uid="MouseJumpThumbnailSize_Edit">
<StackPanel Margin="0,12,0,0" Spacing="16">
<NumberBox
x:Uid="MouseUtils_MouseJump_ThumbnailSize_Edit_Width"
Width="140"
Minimum="160"
SpinButtonPlacementMode="Compact"
Value="{x:Bind ViewModel.MouseJumpThumbnailSize.Width, Mode=TwoWay}" />
<NumberBox
x:Uid="MouseUtils_MouseJump_ThumbnailSize_Edit_Height"
Width="140"
Minimum="120"
SpinButtonPlacementMode="Compact"
Value="{x:Bind ViewModel.MouseJumpThumbnailSize.Height, Mode=TwoWay}" />
</StackPanel>
</Flyout>
</Button.Flyout>
</Button>
</StackPanel>
</labs:SettingsCard>
</controls:SettingsGroup>
<controls:SettingsGroup x:Uid="MouseUtils_MousePointerCrosshairs">