Skip to content

Commit 6cdb321

Browse files
committed
fix(Core): define GetScreen for Windows
Fixes region capture on Windows
1 parent ed80e62 commit 6cdb321

4 files changed

Lines changed: 129 additions & 13 deletions

File tree

SnapX.Avalonia/Views/RegionSelectorWindow.axaml.cs

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,24 +36,50 @@ public RegionSelectorWindow(RegionSelectorViewModel vm)
3636
{
3737
DataContext = vm;
3838
InitializeComponent();
39+
3940
_selectionRect = this.FindControl<Rectangle>("SelectionRect");
4041
_infoBox = this.FindControl<TextBox>("InfoBox");
4142
_canvas = this.FindControl<Canvas>("Canvas");
4243

43-
// var workingArea = Screens.All
44-
// .Select(screen => screen.Bounds)
45-
// .Aggregate((acc, next) => acc.Union(next));
46-
var (x, y, width, height) = Methods.GetActiveScreen().GetAwaiter().GetResult();
47-
DebugHelper.WriteLine($"VirtualScreen details: X is {x} Y is {y} Width is {width} Height is {height}");
48-
Position = new PixelPoint(x, y);
49-
// Width = width;
50-
// height = width;
51-
_canvas.Width = width;
52-
_canvas.Height = height;
53-
var viewBox = _canvas.Parent as Viewbox;
54-
viewBox.Width = width;
55-
viewBox.Height = height;
44+
// Set initial state to invisible/minimized to prevent flicker
45+
// until the async position logic finishes.
46+
Opacity = 0;
47+
}
48+
protected override async void OnOpened(EventArgs e)
49+
{
50+
base.OnOpened(e);
51+
52+
await SetupWindowBoundsAsync();
53+
54+
Opacity = 1;
55+
}
56+
private async Task SetupWindowBoundsAsync()
57+
{
58+
var bounds = await Task.Run(async() =>
59+
{
60+
var (x, y, width, height) = await Methods.GetActiveScreen();
61+
DebugHelper.WriteLine($"VirtualScreen details: X is {x} Y is {y} Width is {width} Height is {height}");
62+
63+
return new PixelRect(x, y, width, height);
64+
});
65+
66+
Dispatcher.UIThread.Post(() =>
67+
{
68+
Position = new PixelPoint(bounds.X, bounds.Y);
69+
Width = bounds.Width;
70+
Height = bounds.Height;
71+
72+
_canvas.Width = bounds.Width;
73+
_canvas.Height = bounds.Height;
74+
75+
if (_canvas.Parent is Viewbox viewBox)
76+
{
77+
viewBox.Width = bounds.Width;
78+
viewBox.Height = bounds.Height;
79+
}
5680

81+
DebugHelper.WriteLine($"Selector Ready: {bounds.Width}x{bounds.Height} at {bounds.X},{bounds.Y}");
82+
});
5783
}
5884
public RegionSelectorWindow() : this(new RegionSelectorViewModel()) { }
5985
private void OnPointerPressed(object? Sender, PointerPressedEventArgs E)

SnapX.Core/NativeMethods.txt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@ GetCursorPos
2020
SetCursorPos
2121
SetForegroundWindow
2222
GetForegroundWindow
23+
MonitorFromPoint
24+
GetMonitorInfo
25+
MONITORINFOEXW
26+
MONITORINFOF_PRIMARY
27+
EnumDisplayMonitors
28+
EnumDisplaySettings
29+
DEVMODEW
30+
ENUM_DISPLAY_SETTINGS_MODE
31+
GetDpiForMonitor
32+
MONITOR_DPI_TYPE
33+
CreateDC
34+
GetDeviceCaps
35+
DeleteDC
36+
GET_DEVICE_CAPS_INDEX
2337
OpenClipboard
2438
CloseClipboard
2539
EmptyClipboard

SnapX.Core/SharpCapture/Windows/WindowsCapture.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using Windows.Win32;
1313
using Windows.Win32.Foundation;
1414
using Windows.Win32.UI.WindowsAndMessaging;
15+
using SnapX.Core.Utils.Native;
1516
using WinRT;
1617

1718

@@ -179,6 +180,8 @@ private static IDirect3DDevice CreateDirect3DDeviceFromVorticeDevice(ID3D11Devic
179180

180181
return device;
181182
}
183+
public override async Task<Rectangle> GetScreen(Point pos) => Methods.NativeAPI.GetScreen(pos)?.Bounds ?? Rectangle.Empty;
184+
182185
private static ID3D11Texture2D Texture2DFromSurface(IDirect3DSurface surface, ID3D11Device device, IntPtr captureHwnd)
183186
{
184187
DebugHelper.WriteLine("=== IDirect3DSurface ===");

SnapX.Core/Utils/Native/WindowsAPI.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
using SnapX.Core.Utils.Extensions;
1111
using Windows.Win32;
1212
using Windows.Win32.Foundation;
13+
using Windows.Win32.Graphics.Gdi;
1314
using Windows.Win32.System.Memory;
15+
using Windows.Win32.UI.HiDpi;
1416
using Windows.Win32.UI.Shell;
1517
using Windows.Win32.UI.WindowsAndMessaging;
1618

@@ -241,6 +243,77 @@ public static Rectangle GetWindowRect(IntPtr hwnd)
241243
return new Rectangle(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
242244
}
243245

246+
public override Screen? GetScreen(Point pos)
247+
{
248+
var pt = new System.Drawing.Point(pos.X, pos.Y);
249+
250+
// 2. Get the HMONITOR handle.
251+
// MONITOR_DEFAULTTONEAREST ensures that even if the point is off-screen,
252+
// it returns the closest monitor rather than null.
253+
var hMonitor = PInvoke.MonitorFromPoint(pt, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
254+
255+
if (hMonitor == IntPtr.Zero)
256+
{
257+
return null;
258+
}
259+
260+
unsafe
261+
{
262+
var info = new MONITORINFOEXW();
263+
info.monitorInfo.cbSize = (uint)Marshal.SizeOf<MONITORINFOEXW>();
264+
265+
if (!PInvoke.GetMonitorInfo(hMonitor, (MONITORINFO*)&info)) return null;
266+
var deviceName = info.szDevice.ToString();
267+
var devMode = new DEVMODEW { dmSize = (ushort)sizeof(DEVMODEW) };
268+
uint refreshRate = 0;
269+
var orientation = ScreenOrientation.Landscape;
270+
if (PInvoke.EnumDisplaySettings(deviceName, ENUM_DISPLAY_SETTINGS_MODE.ENUM_CURRENT_SETTINGS, ref devMode))
271+
{
272+
refreshRate = devMode.dmDisplayFrequency;
273+
orientation = devMode.Anonymous1.Anonymous2.dmDisplayOrientation switch
274+
{
275+
DEVMODE_DISPLAY_ORIENTATION.DMDO_90 => ScreenOrientation.Portrait,
276+
DEVMODE_DISPLAY_ORIENTATION.DMDO_270 => ScreenOrientation.Portrait,
277+
_ => ScreenOrientation.Landscape
278+
};
279+
}
280+
PInvoke.GetDpiForMonitor(hMonitor, MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpiX, out var _);
281+
var scaleFactor = dpiX / 96.0;
282+
var hdc = PInvoke.CreateDCW(null, deviceName, null, null);
283+
double diagonalInches = 0;
284+
if (!hdc.IsNull)
285+
{
286+
var widthMm = PInvoke.GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.HORZSIZE);
287+
var heightMm = PInvoke.GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.VERTSIZE);
288+
289+
var widthInches = widthMm / 25.4;
290+
var heightInches = heightMm / 25.4;
291+
diagonalInches = Math.Sqrt(Math.Pow(widthInches, 2) + Math.Pow(heightInches, 2));
292+
293+
PInvoke.DeleteDC(hdc);
294+
}
295+
return new Screen
296+
{
297+
Id = deviceName,
298+
// Index = index, unknown
299+
Name = deviceName,
300+
Bounds = new Rectangle(
301+
info.monitorInfo.rcMonitor.left,
302+
info.monitorInfo.rcMonitor.top,
303+
info.monitorInfo.rcMonitor.right - info.monitorInfo.rcMonitor.left,
304+
info.monitorInfo.rcMonitor.bottom - info.monitorInfo.rcMonitor.top
305+
),
306+
RefreshRate = refreshRate,
307+
Orientation = orientation,
308+
DPI = dpiX,
309+
ScaleFactor = scaleFactor,
310+
DiagonalSizeInches = Math.Round(diagonalInches, 1),
311+
IsPrimary = (info.monitorInfo.dwFlags & PInvoke.MONITORINFOF_PRIMARY) != 0,
312+
SessionType = SessionType.Windows
313+
};
314+
}
315+
}
316+
244317
// Beginning of IntegrationHelper class being integrated into WindowsAPI class
245318

246319
private static readonly string ApplicationPath = $"\"{AppDomain.CurrentDomain.BaseDirectory}\"";

0 commit comments

Comments
 (0)