[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/fancyzones/lib/FancyZonesWinHookEventIDs\.h$
^src/modules/imageresizer/dll/ContextMenuHandler\.rgs$ ^src/modules/imageresizer/dll/ContextMenuHandler\.rgs$
^src/modules/imageresizer/dll/ImageResizerExt\.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/powerrename/testapp/PowerRenameTest\.vcxproj\.filters$
^src/modules/previewpane/PreviewPaneUnitTests/HelperFiles/MarkdownWithHTMLImageTag\.txt$ ^src/modules/previewpane/PreviewPaneUnitTests/HelperFiles/MarkdownWithHTMLImageTag\.txt$
^src/modules/previewpane/UnitTests-MarkdownPreviewHandler/HelperFiles/MarkdownWithHTMLImageTag.txt$ ^src/modules/previewpane/UnitTests-MarkdownPreviewHandler/HelperFiles/MarkdownWithHTMLImageTag.txt$

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,95 +7,16 @@ using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
using System.Linq; using System.Linq;
using System.Windows.Forms; using MouseJumpUI.Models.Drawing;
using MouseJumpUI.Drawing.Models; using MouseJumpUI.NativeMethods;
using MouseJumpUI.NativeMethods.Core; using static MouseJumpUI.NativeMethods.Core;
using MouseJumpUI.NativeWrappers;
namespace MouseJumpUI.Helpers; namespace MouseJumpUI.Helpers;
internal static class DrawingHelper 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> /// <summary>
/// Resize and position the specified form. /// Draw the gradient-filled preview background.
/// </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.
/// </summary> /// </summary>
public static void DrawPreviewBackground( public static void DrawPreviewBackground(
Graphics previewGraphics, RectangleInfo previewBounds, IEnumerable<RectangleInfo> screenBounds) Graphics previewGraphics, RectangleInfo previewBounds, IEnumerable<RectangleInfo> screenBounds)
@@ -127,6 +48,11 @@ internal static class DrawingHelper
if (desktopHdc.IsNull) if (desktopHdc.IsNull)
{ {
desktopHdc = User32.GetWindowDC(desktopHwnd); 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) 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; desktopHwnd = HWND.Null;
@@ -143,14 +74,20 @@ internal static class DrawingHelper
/// <summary> /// <summary>
/// Checks if the device context handle exists, and creates a new one from the /// Checks if the device context handle exists, and creates a new one from the
/// Graphics object if not. /// specified Graphics object if not.
/// </summary> /// </summary>
public static void EnsurePreviewDeviceContext(Graphics previewGraphics, ref HDC previewHdc) public static void EnsurePreviewDeviceContext(Graphics previewGraphics, ref HDC previewHdc)
{ {
if (previewHdc.IsNull) if (previewHdc.IsNull)
{ {
previewHdc = new HDC(previewGraphics.GetHdc()); 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. /// Draw placeholder images for any non-activated screens on the preview.
/// Will release the specified device context handle if it needs to draw anything. /// Will release the specified device context handle if it needs to draw anything.
/// </summary> /// </summary>
public static void DrawPreviewPlaceholders( public static void DrawPreviewScreenPlaceholders(
Graphics previewGraphics, IEnumerable<RectangleInfo> screenBounds) Graphics previewGraphics, IEnumerable<RectangleInfo> screenBounds)
{ {
// we can exclude the activated screen because we've already draw // we can exclude the activated screen because we've already draw
@@ -183,7 +120,7 @@ internal static class DrawingHelper
} }
/// <summary> /// <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> /// </summary>
public static void DrawPreviewScreen( public static void DrawPreviewScreen(
HDC sourceHdc, HDC sourceHdc,
@@ -193,7 +130,7 @@ internal static class DrawingHelper
{ {
var source = sourceBounds.ToRectangle(); var source = sourceBounds.ToRectangle();
var target = targetBounds.ToRectangle(); var target = targetBounds.ToRectangle();
_ = Gdi32.StretchBlt( var result = Gdi32.StretchBlt(
targetHdc, targetHdc,
target.X, target.X,
target.Y, target.Y,
@@ -204,7 +141,12 @@ internal static class DrawingHelper
source.Y, source.Y,
source.Width, source.Width,
source.Height, source.Height,
MouseJumpUI.NativeMethods.Gdi32.ROP_CODE.SRCCOPY); Gdi32.ROP_CODE.SRCCOPY);
if (!result)
{
throw new InvalidOperationException(
$"{nameof(Gdi32.StretchBlt)} returned {result.Value}");
}
} }
/// <summary> /// <summary>
@@ -220,7 +162,7 @@ internal static class DrawingHelper
{ {
var source = sourceBounds[i].ToRectangle(); var source = sourceBounds[i].ToRectangle();
var target = targetBounds[i].ToRectangle(); var target = targetBounds[i].ToRectangle();
_ = Gdi32.StretchBlt( var result = Gdi32.StretchBlt(
targetHdc, targetHdc,
target.X, target.X,
target.Y, target.Y,
@@ -231,7 +173,12 @@ internal static class DrawingHelper
source.Y, source.Y,
source.Width, source.Width,
source.Height, 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. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // 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 System.Windows.Forms;
using MouseJumpUI.Drawing.Models; using MouseJumpUI.Models.Drawing;
using MouseJumpUI.NativeMethods;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.Helpers; namespace MouseJumpUI.Helpers;
@@ -26,13 +29,33 @@ internal static class MouseHelper
.Offset(desktopBounds.Location); .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> /// <summary>
/// Moves the cursor to the specified location. /// Moves the cursor to the specified location.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// See https://github.com/mikeclayton/FancyMouse/pull/3 /// See https://github.com/mikeclayton/FancyMouse/pull/3
/// </remarks> /// </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 // 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 // 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 // setting the position a second time seems to fix this and moves the
// cursor to the expected location (b) // cursor to the expected location (b)
var point = location.ToPoint(); var point = location.ToPoint();
Cursor.Position = point; for (var i = 0; i < 2; i++)
Cursor.Position = point; {
var result = User32.SetCursorPos(point.X, point.Y);
if (!result)
{
throw new Win32Exception(
Marshal.GetLastWin32Error());
}
}
// temporary workaround for issue #1273
MouseHelper.SimulateMouseMovementEvent(location);
} }
/// <summary> /// <summary>
@@ -62,26 +95,43 @@ internal static class MouseHelper
/// See https://github.com/microsoft/PowerToys/issues/24523 /// See https://github.com/microsoft/PowerToys/issues/24523
/// https://github.com/microsoft/PowerToys/pull/24527 /// https://github.com/microsoft/PowerToys/pull/24527
/// </remarks> /// </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, new(
data = new NativeMethods.InputUnion type: User32.INPUT_TYPE.INPUT_MOUSE,
{ data: new User32.INPUT.DUMMYUNIONNAME(
mi = new NativeMethods.MOUSEINPUT mi: new User32.MOUSEINPUT(
{ dx: (int)MouseHelper.CalculateAbsoluteCoordinateX(location.X),
dx = NativeMethods.CalculateAbsoluteCoordinateX(location.X), dy: (int)MouseHelper.CalculateAbsoluteCoordinateY(location.Y),
dy = NativeMethods.CalculateAbsoluteCoordinateY(location.Y), mouseData: 0,
mouseData = 0, dwFlags: User32.MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE | User32.MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE,
dwFlags = (uint)NativeMethods.MOUSE_INPUT_FLAGS.MOUSEEVENTF_MOVE time: 0,
| (uint)NativeMethods.MOUSE_INPUT_FLAGS.MOUSEEVENTF_ABSOLUTE, dwExtraInfo: ULONG_PTR.Null))),
time = 0,
dwExtraInfo = 0,
},
},
}; };
var inputs = new NativeMethods.INPUT[] { mouseMoveInput }; var result = User32.SendInput(
_ = NativeMethods.SendInput(1, inputs, NativeMethods.INPUT.Size); (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.Linq;
using System.Windows.Forms; using System.Windows.Forms;
using ManagedCommon; using ManagedCommon;
using MouseJumpUI.Drawing.Models; using Microsoft.PowerToys.Settings.UI.Library;
using MouseJumpUI.Helpers; using MouseJumpUI.Helpers;
using MouseJumpUI.NativeMethods.Core; using MouseJumpUI.Models.Drawing;
using MouseJumpUI.Models.Layout;
using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI; namespace MouseJumpUI;
internal partial class MainForm : Form internal partial class MainForm : Form
{ {
public MainForm() public MainForm(MouseJumpSettings settings)
{ {
this.InitializeComponent(); this.InitializeComponent();
this.Settings = settings ?? throw new ArgumentNullException(nameof(settings));
this.ShowThumbnail(); this.ShowThumbnail();
} }
public MouseJumpSettings Settings
{
get;
}
private void MainForm_Load(object sender, EventArgs e) private void MainForm_Load(object sender, EventArgs e)
{ {
} }
@@ -32,6 +40,65 @@ internal partial class MainForm : Form
if (e.KeyCode == Keys.Escape) if (e.KeyCode == Keys.Escape)
{ {
this.OnDeactivate(EventArgs.Empty); 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) if (mouseEventArgs.Button == MouseButtons.Left)
{ {
// plain click - move mouse pointer // plain click - move mouse pointer
var virtualScreen = ScreenHelper.GetVirtualScreen();
var scaledLocation = MouseHelper.GetJumpLocation( var scaledLocation = MouseHelper.GetJumpLocation(
new PointInfo(mouseEventArgs.X, mouseEventArgs.Y), new PointInfo(mouseEventArgs.X, mouseEventArgs.Y),
new SizeInfo(this.Thumbnail.Size), new SizeInfo(this.Thumbnail.Size),
new RectangleInfo(SystemInformation.VirtualScreen)); virtualScreen);
Logger.LogInfo($"scaled location = {scaledLocation}"); Logger.LogInfo($"scaled location = {scaledLocation}");
MouseHelper.JumpCursor(scaledLocation); MouseHelper.SetCursorPosition(scaledLocation);
// Simulate mouse input for handlers that won't just catch the Cursor change
MouseHelper.SimulateMouseMovementEvent(scaledLocation.ToPoint());
Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new Telemetry.MouseJumpTeleportCursorEvent()); Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new Telemetry.MouseJumpTeleportCursorEvent());
} }
@@ -76,57 +141,93 @@ internal partial class MainForm : Form
public void ShowThumbnail() public void ShowThumbnail()
{ {
var screens = Screen.AllScreens; var stopwatch = Stopwatch.StartNew();
foreach (var i in Enumerable.Range(0, screens.Length)) 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( Logger.LogInfo(string.Join(
'\n', '\n',
$"screen[{i}] = \"{screen.DeviceName}\"", $"screen[{screen.Number}]",
$"\tprimary = {screen.Primary}", $"\tprimary = {screen.Screen.Primary}",
$"\tbounds = {screen.Bounds}", $"\tdisplay area = {screen.Screen.DisplayArea}",
$"\tworking area = {screen.WorkingArea}")); $"\tworking area = {screen.Screen.WorkingArea}"));
} }
// collect together some values that we need for calculating layout // 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( var layoutConfig = new LayoutConfig(
virtualScreen: SystemInformation.VirtualScreen, virtualScreenBounds: ScreenHelper.GetVirtualScreen(),
screenBounds: Screen.AllScreens.Select(screen => screen.Bounds), screens: screens.Select(item => item.Screen).ToList(),
activatedLocation: activatedLocation, activatedLocation: activatedLocation,
activatedScreen: Array.IndexOf(Screen.AllScreens, Screen.FromPoint(activatedLocation)), activatedScreenIndex: activatedScreenIndex,
maximumFormSize: new Size(1600, 1200), activatedScreenNumber: activatedScreenIndex + 1,
formPadding: this.panel1.Padding, maximumFormSize: new(
previewPadding: new Padding(0)); 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( Logger.LogInfo(string.Join(
'\n', '\n',
$"Layout config", $"Layout config",
$"-------------", $"-------------",
$"virtual screen = {layoutConfig.VirtualScreen}", $"virtual screen = {layoutConfig.VirtualScreenBounds}",
$"activated location = {layoutConfig.ActivatedLocation}", $"activated location = {layoutConfig.ActivatedLocation}",
$"activated screen = {layoutConfig.ActivatedScreen}", $"activated screen index = {layoutConfig.ActivatedScreenIndex}",
$"maximum form size = {layoutConfig.MaximumFormSize}", $"activated screen number = {layoutConfig.ActivatedScreenNumber}",
$"form padding = {layoutConfig.FormPadding}", $"maximum form size = {layoutConfig.MaximumFormSize}",
$"preview padding = {layoutConfig.PreviewPadding}")); $"form padding = {layoutConfig.FormPadding}",
$"preview padding = {layoutConfig.PreviewPadding}"));
// calculate the layout coordinates for everything // calculate the layout coordinates for everything
var layoutInfo = DrawingHelper.CalculateLayoutInfo(layoutConfig); var layoutInfo = LayoutHelper.CalculateLayoutInfo(layoutConfig);
Logger.LogInfo(string.Join( Logger.LogInfo(string.Join(
'\n', '\n',
$"Layout info", $"Layout info",
$"-----------", $"-----------",
$"form bounds = {layoutInfo.FormBounds}", $"form bounds = {layoutInfo.FormBounds}",
$"preview bounds = {layoutInfo.PreviewBounds}", $"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 // initialize the preview image
var preview = new Bitmap( var preview = new Bitmap(
(int)layoutInfo.PreviewBounds.Width, (int)layoutInfo.PreviewBounds.Width,
(int)layoutInfo.PreviewBounds.Height, (int)layoutInfo.PreviewBounds.Height,
PixelFormat.Format32bppArgb); PixelFormat.Format32bppArgb);
this.Thumbnail.Image = preview; form.Thumbnail.Image = preview;
using var previewGraphics = Graphics.FromImage(preview); using var previewGraphics = Graphics.FromImage(preview);
@@ -137,49 +238,51 @@ internal partial class MainForm : Form
var previewHdc = HDC.Null; var previewHdc = HDC.Null;
try 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); 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.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 var placeholdersDrawn = false;
// to capture the remaining screenshot images for (var i = 0; i < sourceScreens.Count; i++)
if (activatedStopwatch.ElapsedMilliseconds > 250)
{ {
var activatedArea = layoutConfig.ScreenBounds[layoutConfig.ActivatedScreen].Area; DrawingHelper.DrawPreviewScreen(
var totalArea = layoutConfig.ScreenBounds.Sum(screen => screen.Area); desktopHdc, previewHdc, sourceScreens[i], targetScreens[i]);
if ((activatedArea / totalArea) < 0.5M)
// 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 // using the Graphics object otherwise we'll get an error from GDI saying
// "Object is currently in use elsewhere" // "Object is currently in use elsewhere"
DrawingHelper.FreePreviewDeviceContext(previewGraphics, ref previewHdc); DrawingHelper.FreePreviewDeviceContext(previewGraphics, ref previewHdc);
DrawingHelper.DrawPreviewPlaceholders(
previewGraphics,
layoutInfo.ScreenBounds.Where((_, idx) => idx != layoutConfig.ActivatedScreen));
MainForm.ShowPreview(this);
}
}
// draw the remaining screen captures (if any) on the preview image if (!placeholdersDrawn)
var sourceScreens = layoutConfig.ScreenBounds.Where((_, idx) => idx != layoutConfig.ActivatedScreen).ToList(); {
if (sourceScreens.Any()) // draw placeholders for any undrawn screens
{ DrawingHelper.DrawPreviewScreenPlaceholders(
DrawingHelper.EnsurePreviewDeviceContext(previewGraphics, ref previewHdc); previewGraphics,
DrawingHelper.DrawPreviewScreens( targetScreens.Where((_, idx) => idx > i));
desktopHdc, placeholdersDrawn = true;
previewHdc, }
sourceScreens,
layoutInfo.ScreenBounds.Where((_, idx) => idx != layoutConfig.ActivatedScreen).ToList()); MainForm.RefreshPreview(form);
MainForm.ShowPreview(this);
// we've still got more screens to draw so open the device context again
DrawingHelper.EnsurePreviewDeviceContext(previewGraphics, ref previewHdc);
}
} }
} }
finally finally
@@ -188,13 +291,11 @@ internal partial class MainForm : Form
DrawingHelper.FreePreviewDeviceContext(previewGraphics, ref previewHdc); DrawingHelper.FreePreviewDeviceContext(previewGraphics, ref previewHdc);
} }
// we have to activate the form to make sure the deactivate event fires MainForm.RefreshPreview(form);
MainForm.ShowPreview(this); stopwatch.Stop();
Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new Telemetry.MouseJumpTeleportCursorEvent());
this.Activate();
} }
private static void ShowPreview(MainForm form) private static void RefreshPreview(MainForm form)
{ {
if (!form.Visible) if (!form.Visible)
{ {

View File

@@ -4,7 +4,7 @@
using System.Windows.Forms; using System.Windows.Forms;
namespace MouseJumpUI.Drawing.Models; namespace MouseJumpUI.Models.Drawing;
/// <summary> /// <summary>
/// Immutable version of a System.Windows.Forms.Padding object with some extra utility methods. /// 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) public PaddingInfo(decimal left, decimal top, decimal right, decimal bottom)
{ {
this.Left = left; this.Left = left;

View File

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

View File

@@ -5,7 +5,7 @@
using System; using System;
using System.Drawing; using System.Drawing;
namespace MouseJumpUI.Drawing.Models; namespace MouseJumpUI.Models.Drawing;
/// <summary> /// <summary>
/// Immutable version of a System.Drawing.Rectangle object with some extra utility methods. /// 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; 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( public RectangleInfo Enlarge(PaddingInfo padding) => new(
this.X + padding.Left, this.X + padding.Left,
this.Y + padding.Top, this.Y + padding.Top,

View File

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

View File

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

View File

@@ -6,8 +6,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using MouseJumpUI.Models.Drawing;
namespace MouseJumpUI.Drawing.Models; namespace MouseJumpUI.Models.Layout;
public sealed class LayoutInfo public sealed class LayoutInfo
{ {
@@ -42,7 +43,7 @@ public sealed class LayoutInfo
set; set;
} }
public RectangleInfo? ActivatedScreen public RectangleInfo? ActivatedScreenBounds
{ {
get; get;
set; set;
@@ -55,7 +56,7 @@ public sealed class LayoutInfo
formBounds: this.FormBounds ?? throw new InvalidOperationException(), formBounds: this.FormBounds ?? throw new InvalidOperationException(),
previewBounds: this.PreviewBounds ?? throw new InvalidOperationException(), previewBounds: this.PreviewBounds ?? throw new InvalidOperationException(),
screenBounds: this.ScreenBounds ?? 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 formBounds,
RectangleInfo previewBounds, RectangleInfo previewBounds,
IEnumerable<RectangleInfo> screenBounds, IEnumerable<RectangleInfo> screenBounds,
RectangleInfo activatedScreen) RectangleInfo activatedScreenBounds)
{ {
this.LayoutConfig = layoutConfig ?? throw new ArgumentNullException(nameof(layoutConfig)); this.LayoutConfig = layoutConfig ?? throw new ArgumentNullException(nameof(layoutConfig));
this.FormBounds = formBounds ?? throw new ArgumentNullException(nameof(formBounds)); this.FormBounds = formBounds ?? throw new ArgumentNullException(nameof(formBounds));
@@ -72,7 +73,7 @@ public sealed class LayoutInfo
this.ScreenBounds = new( this.ScreenBounds = new(
(screenBounds ?? throw new ArgumentNullException(nameof(screenBounds))) (screenBounds ?? throw new ArgumentNullException(nameof(screenBounds)))
.ToList()); .ToList());
this.ActivatedScreen = activatedScreen ?? throw new ArgumentNullException(nameof(activatedScreen)); this.ActivatedScreenBounds = activatedScreenBounds ?? throw new ArgumentNullException(nameof(activatedScreenBounds));
} }
/// <summary> /// <summary>
@@ -104,7 +105,7 @@ public sealed class LayoutInfo
get; get;
} }
public RectangleInfo ActivatedScreen public RectangleInfo ActivatedScreenBounds
{ {
get; 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\GPOWrapper\GPOWrapper.vcxproj" />
<ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" /> <ProjectReference Include="..\..\..\common\interop\PowerToys.Interop.vcxproj" />
<ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" /> <ProjectReference Include="..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -2,35 +2,43 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
namespace MouseJumpUI.NativeMethods.Core; namespace MouseJumpUI.NativeMethods;
/// <summary> internal static partial class Core
/// 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; /// <summary>
/// A Boolean variable (should be TRUE or FALSE).
public BOOL(int value) /// 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
{ {
this.Value = value; public readonly int Value;
public BOOL(int value)
{
this.Value = value;
}
public BOOL(bool value)
{
this.Value = value ? 1 : 0;
}
public static implicit operator bool(BOOL value) => value.Value != 0;
public static implicit operator BOOL(bool value) => new(value);
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})";
}
} }
public BOOL(bool value)
{
this.Value = value ? 1 : 0;
}
public static implicit operator bool(BOOL value) => value.Value != 0;
public static implicit operator BOOL(bool value) => new(value);
public static implicit operator int(BOOL value) => value.Value;
public static implicit operator BOOL(int value) => new(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,26 +4,34 @@
using System; using System;
namespace MouseJumpUI.NativeMethods.Core; namespace MouseJumpUI.NativeMethods;
/// <summary> internal static partial class Core
/// 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); /// <summary>
/// A handle to a device context (DC).
public readonly IntPtr Value; /// This type is declared in WinDef.h as follows:
/// typedef HANDLE HDC;
public HDC(IntPtr value) /// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct HDC
{ {
this.Value = value; public static readonly HDC Null = new(IntPtr.Zero);
}
public bool IsNull => this.Value == HDC.Null.Value; public readonly IntPtr Value;
public HDC(IntPtr value)
{
this.Value = value;
}
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,26 +4,34 @@
using System; using System;
namespace MouseJumpUI.NativeMethods.Core; namespace MouseJumpUI.NativeMethods;
/// <summary> internal static partial class Core
/// 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); /// <summary>
/// A handle to a window.
public readonly IntPtr Value; /// This type is declared in WinDef.h as follows:
/// typedef HANDLE HWND;
public HWND(IntPtr value) /// </summary>
/// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
/// </remarks>
internal readonly struct HWND
{ {
this.Value = value; public static readonly HWND Null = new(IntPtr.Zero);
}
public bool IsNull => this.Value == HWND.Null.Value; public readonly IntPtr Value;
public HWND(IntPtr value)
{
this.Value = value;
}
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> /// <remarks>
/// See https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-bitblt /// See https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-bitblt
/// </remarks> /// </remarks>
public enum ROP_CODE : uint internal enum ROP_CODE : uint
{ {
BLACKNESS = 0x00000042, BLACKNESS = 0x00000042,
CAPTUREBLT = 0x40000000, CAPTUREBLT = 0x40000000,

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using MouseJumpUI.NativeMethods.Core; using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods; 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 /// See https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-stretchblt
/// </remarks> /// </remarks>
[LibraryImport(Libraries.Gdi32)] [LibraryImport(Libraries.Gdi32)]
public static partial BOOL StretchBlt( internal static partial BOOL StretchBlt(
HDC hdcDest, HDC hdcDest,
int xDest, int xDest,
int yDest, 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. // See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using MouseJumpUI.NativeMethods.Core; using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods; 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 /// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowdc
/// </remarks> /// </remarks>
[LibraryImport(Libraries.User32)] [LibraryImport(Libraries.User32)]
public static partial HDC GetWindowDC( internal static partial HDC GetWindowDC(
HWND hWnd); 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. // See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using MouseJumpUI.NativeMethods.Core; using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods; 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 /// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-releasedc
/// </remarks> /// </remarks>
[LibraryImport(Libraries.User32)] [LibraryImport(Libraries.User32)]
public static partial int ReleaseDC( internal static partial int ReleaseDC(
HWND hWnd, HWND hWnd,
HDC hDC); 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. // See the LICENSE file in the project root for more information.
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using MouseJumpUI.NativeMethods.Core; using static MouseJumpUI.NativeMethods.Core;
namespace MouseJumpUI.NativeMethods; 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 /// See https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdesktopwindow
/// </remarks> /// </remarks>
[LibraryImport(Libraries.User32)] [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,15 +3,18 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.IO;
using System.Text.Json;
using System.Windows.Forms; using System.Windows.Forms;
using ManagedCommon; using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
namespace MouseJumpUI; namespace MouseJumpUI;
internal static class Program internal static class Program
{ {
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
/// </summary> /// </summary>
[STAThread] [STAThread]
private static void Main() private static void Main()
@@ -35,6 +38,34 @@ internal static class Program
return; 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")] [JsonPropertyName("activation_shortcut")]
public HotkeySettings ActivationShortcut { get; set; } public HotkeySettings ActivationShortcut { get; set; }
[JsonPropertyName("thumbnail_size")]
public MouseJumpThumbnailSize ThumbnailSize { get; set; }
public MouseJumpProperties() public MouseJumpProperties()
{ {
ActivationShortcut = new HotkeySettings(true, false, false, true, 0x44); 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"> <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> <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>
<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> </root>

View File

@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.ComponentModel;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using global::PowerToys.GPOWrapper; using global::PowerToys.GPOWrapper;
using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library;
@@ -86,6 +87,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
} }
MouseJumpSettingsConfig = mouseJumpSettingsRepository.SettingsConfig; MouseJumpSettingsConfig = mouseJumpSettingsRepository.SettingsConfig;
MouseJumpSettingsConfig.Properties.ThumbnailSize.PropertyChanged += MouseJumpThumbnailSizePropertyChanged;
if (mousePointerCrosshairsSettingsRepository == null) 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) public void NotifyMouseJumpPropertyChanged([CallerMemberName] string propertyName = null)
{ {
OnPropertyChanged(propertyName); OnPropertyChanged(propertyName);

View File

@@ -229,6 +229,81 @@
MinWidth="{StaticResource SettingActionControlMinWidth}" MinWidth="{StaticResource SettingActionControlMinWidth}"
HotkeySettings="{x:Bind Path=ViewModel.MouseJumpActivationShortcut, Mode=TwoWay}" /> HotkeySettings="{x:Bind Path=ViewModel.MouseJumpActivationShortcut, Mode=TwoWay}" />
</labs:SettingsCard> </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>
<controls:SettingsGroup x:Uid="MouseUtils_MousePointerCrosshairs"> <controls:SettingsGroup x:Uid="MouseUtils_MousePointerCrosshairs">