diff --git a/Directory.Packages.props b/Directory.Packages.props index 3479ca4..d955bc6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,8 +3,8 @@ true - - - + + + \ No newline at end of file diff --git a/src/ScreenGrab/Extensions/DapploExtensions.cs b/src/ScreenGrab/Extensions/DapploExtensions.cs index 39c6d1c..a3681fc 100644 --- a/src/ScreenGrab/Extensions/DapploExtensions.cs +++ b/src/ScreenGrab/Extensions/DapploExtensions.cs @@ -8,9 +8,9 @@ public static class DapploExtensions public static Point ScaledCenterPoint(this DisplayInfo displayInfo) { Rect displayRect = displayInfo.Bounds; - NativeMethods.GetScaleFactorForMonitor(displayInfo.MonitorHandle, out uint scaleFactor); - double scaleFraction = scaleFactor / 100.0; - Point rawCenter = displayRect.CenterPoint(); + NativeMethods.GetScaleFactorForMonitor(displayInfo.MonitorHandle, out var scaleFactor); + var scaleFraction = scaleFactor / 100.0; + var rawCenter = displayRect.CenterPoint(); Point displayScaledCenterPoint = new(rawCenter.X / scaleFraction, rawCenter.Y / scaleFraction); return displayScaledCenterPoint; } @@ -18,8 +18,8 @@ public static Point ScaledCenterPoint(this DisplayInfo displayInfo) public static Rect ScaledBounds(this DisplayInfo displayInfo) { Rect displayRect = displayInfo.Bounds; - NativeMethods.GetScaleFactorForMonitor(displayInfo.MonitorHandle, out uint scaleFactor); - double scaleFraction = scaleFactor / 100.0; + NativeMethods.GetScaleFactorForMonitor(displayInfo.MonitorHandle, out var scaleFactor); + var scaleFraction = scaleFactor / 100.0; // Scale size and position Rect scaledBounds = new( diff --git a/src/ScreenGrab/Extensions/ShapeExtensions.cs b/src/ScreenGrab/Extensions/ShapeExtensions.cs index 51b40aa..3f9af4b 100644 --- a/src/ScreenGrab/Extensions/ShapeExtensions.cs +++ b/src/ScreenGrab/Extensions/ShapeExtensions.cs @@ -1,5 +1,6 @@ using System.Drawing; using System.Windows; +using Point = System.Windows.Point; namespace ScreenGrab.Extensions; @@ -18,43 +19,43 @@ public static Rectangle AsRectangle(this Rect rect) public static Rect GetScaledDownByDpi(this Rect rect, DpiScale dpi) { return new Rect(rect.X / dpi.DpiScaleX, - rect.Y / dpi.DpiScaleY, - rect.Width / dpi.DpiScaleX, - rect.Height / dpi.DpiScaleY); + rect.Y / dpi.DpiScaleY, + rect.Width / dpi.DpiScaleX, + rect.Height / dpi.DpiScaleY); } public static Rect GetScaledUpByDpi(this Rect rect, DpiScale dpi) { return new Rect(rect.X * dpi.DpiScaleX, - rect.Y * dpi.DpiScaleY, - rect.Width * dpi.DpiScaleX, - rect.Height * dpi.DpiScaleY); + rect.Y * dpi.DpiScaleY, + rect.Width * dpi.DpiScaleX, + rect.Height * dpi.DpiScaleY); } - public static Rect GetScaledUpByFraction(this Rect rect, Double scaleFactor) + public static Rect GetScaledUpByFraction(this Rect rect, double scaleFactor) { return new Rect(rect.X * scaleFactor, - rect.Y * scaleFactor, - rect.Width * scaleFactor, - rect.Height * scaleFactor); + rect.Y * scaleFactor, + rect.Width * scaleFactor, + rect.Height * scaleFactor); } - public static Rect GetScaleSizeByFraction(this Rect rect, Double scaleFactor) + public static Rect GetScaleSizeByFraction(this Rect rect, double scaleFactor) { return new Rect(rect.X, - rect.Y, - rect.Width * scaleFactor, - rect.Height * scaleFactor); + rect.Y, + rect.Width * scaleFactor, + rect.Height * scaleFactor); } public static bool IsGood(this Rect rect) { - if (double.IsNaN(rect.X) + if (double.IsNaN(rect.X) || double.IsNegativeInfinity(rect.X) || double.IsPositiveInfinity(rect.X)) return false; - - if (double.IsNaN(rect.Y) + + if (double.IsNaN(rect.Y) || double.IsNegativeInfinity(rect.Y) || double.IsPositiveInfinity(rect.Y)) return false; @@ -74,10 +75,10 @@ public static bool IsGood(this Rect rect) return true; } - public static System.Windows.Point CenterPoint(this Rect rect) + public static Point CenterPoint(this Rect rect) { - double x = rect.Left + (rect.Width / 2); - double y = rect.Top + (rect.Height / 2); - return new(x, y); + var x = rect.Left + rect.Width / 2; + var y = rect.Top + rect.Height / 2; + return new Point(x, y); } } \ No newline at end of file diff --git a/src/ScreenGrab/Extensions/WpfExtensions.cs b/src/ScreenGrab/Extensions/WpfExtensions.cs index 8d00659..aed32a6 100644 --- a/src/ScreenGrab/Extensions/WpfExtensions.cs +++ b/src/ScreenGrab/Extensions/WpfExtensions.cs @@ -1,7 +1,6 @@ -using System; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Interop; -using System.Runtime.InteropServices; namespace ScreenGrab.Extensions; @@ -13,21 +12,22 @@ public static Point GetAbsolutePosition(this Window w) return new Point(w.Left, w.Top); Int32Rect r; - bool multimonSupported = OSInterop.GetSystemMetrics(OSInterop.SM_CMONITORS) != 0; + var multimonSupported = OSInterop.GetSystemMetrics(OSInterop.SM_CMONITORS) != 0; if (!multimonSupported) { - OSInterop.RECT rc = new OSInterop.RECT(); + var rc = new OSInterop.RECT(); OSInterop.SystemParametersInfo(48, 0, ref rc, 0); r = new Int32Rect(rc.left, rc.top, rc.width, rc.height); } else { - WindowInteropHelper helper = new WindowInteropHelper(w); - IntPtr hmonitor = OSInterop.MonitorFromWindow(new HandleRef(null, helper.EnsureHandle()), 2); - OSInterop.MONITORINFOEX info = new OSInterop.MONITORINFOEX(); + var helper = new WindowInteropHelper(w); + var hmonitor = OSInterop.MonitorFromWindow(new HandleRef(null, helper.EnsureHandle()), 2); + var info = new OSInterop.MONITORINFOEX(); OSInterop.GetMonitorInfo(new HandleRef(null, hmonitor), info); r = new Int32Rect(info.rcMonitor.left, info.rcMonitor.top, info.rcMonitor.width, info.rcMonitor.height); } + return new Point(r.X, r.Y); } } \ No newline at end of file diff --git a/src/ScreenGrab/Grab.cs b/src/ScreenGrab/Grab.cs new file mode 100644 index 0000000..8929a2f --- /dev/null +++ b/src/ScreenGrab/Grab.cs @@ -0,0 +1,39 @@ +using System.Drawing; +using System.Windows; +using Dapplo.Windows.User32; +using ScreenGrab.Extensions; + +namespace ScreenGrab; + +public class Grab +{ + public Action? OnImageCaptured { get; set; } + + public void Capture() + { + var allDisplayInfos = DisplayInfo.AllDisplayInfos; + var allScreenGrab = Application.Current.Windows.OfType().ToList(); + var numberOfScreenGrabWindowsToCreate = allDisplayInfos.Length - allScreenGrab.Count; + + for (var i = 0; i < numberOfScreenGrabWindowsToCreate; i++) + allScreenGrab.Add(new ScreenGrabView(OnImageCaptured)); + + const double sideLength = 40; + + foreach (var (displayInfo, screenGrab) in allDisplayInfos.Zip(allScreenGrab, + (displayInfo, screenGrab) => (displayInfo, screenGrab))) + { + screenGrab.WindowStartupLocation = WindowStartupLocation.Manual; + screenGrab.Width = sideLength; + screenGrab.Height = sideLength; + screenGrab.WindowState = WindowState.Normal; + + var screenCenterPoint = displayInfo.ScaledCenterPoint(); + screenGrab.Left = screenCenterPoint.X - sideLength / 2; + screenGrab.Top = screenCenterPoint.Y - sideLength / 2; + + screenGrab.Show(); + screenGrab.Activate(); + } + } +} \ No newline at end of file diff --git a/src/ScreenGrab/Grab.xaml b/src/ScreenGrab/Grab.xaml deleted file mode 100644 index 5c3e7c0..0000000 --- a/src/ScreenGrab/Grab.xaml +++ /dev/null @@ -1,240 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/ScreenGrab/Grab.xaml.cs b/src/ScreenGrab/Grab.xaml.cs deleted file mode 100644 index d8d8c90..0000000 --- a/src/ScreenGrab/Grab.xaml.cs +++ /dev/null @@ -1,302 +0,0 @@ -using System.Drawing; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; -using Dapplo.Windows.User32; -using ScreenGrab.Extensions; -using ScreenGrab.Utilities; -using Point = System.Windows.Point; - -namespace ScreenGrab; - -public partial class Grab -{ - #region Fields - - private Point clickedPoint = new(); - private TextBox? destinationTextBox; - private DpiScale? dpiScale; - private bool isSelecting = false; - private bool isShiftDown = false; - private Border selectBorder = new(); - private double selectLeft; - private double selectTop; - private Point shiftPoint = new(); - private double xShiftDelta; - private double yShiftDelta; - private bool _isFreeze = true; - - #endregion Fields - - #region Constructors - - public Grab() - { - InitializeComponent(); - } - - #endregion Constructors - - #region Properties - private DisplayInfo? CurrentScreen { get; set; } - - public Bitmap Image { get; set; } - - #endregion Properties - - #region Window Events - - public void SetImageToBackground() - { - BackgroundImage.Source = null; - BackgroundImage.Source = ImageMethods.GetWindowBoundsImage(this); - BackgroundBrush.Opacity = 0.2; - } - - private async void FreezeUnfreeze(bool isActivate) - { - if (isActivate) - { - BackgroundBrush.Opacity = 0; - await Task.Delay(150); - SetImageToBackground(); - } - else - { - BackgroundImage.Source = null; - } - } - - private void FullscreenGrab_KeyDown(object sender, KeyEventArgs e) - { - switch (e.Key) - { - case Key.Escape: - DialogResult = false; - Close(); - break; - case Key.F: - //TODO: 添加全局变量控制显隐 - _isFreeze = !_isFreeze; - FreezeUnfreeze(_isFreeze); - break; - default: - break; - } - } - - private void FullscreenGrab_KeyUp(object sender, KeyEventArgs e) - { - switch (e.Key) - { - case Key.LeftShift: - case Key.RightShift: - isShiftDown = false; - clickedPoint = new Point(clickedPoint.X + xShiftDelta, clickedPoint.Y + yShiftDelta); - break; - default: - break; - } - } - - private void Window_Closed(object? sender, EventArgs e) - { - Close(); - - GC.Collect(); - } - - private void Window_Loaded(object sender, RoutedEventArgs e) - { - WindowState = WindowState.Maximized; - FullWindow.Rect = new System.Windows.Rect(0, 0, Width, Height); - KeyDown += FullscreenGrab_KeyDown; - KeyUp += FullscreenGrab_KeyUp; - - SetImageToBackground(); - -#if DEBUG - Topmost = false; -#endif - } - - private void Window_Unloaded(object sender, RoutedEventArgs e) - { - BackgroundImage.Source = null; - BackgroundImage.UpdateLayout(); - CurrentScreen = null; - dpiScale = null; - - Loaded -= Window_Loaded; - Unloaded -= Window_Unloaded; - - RegionClickCanvas.MouseDown -= RegionClickCanvas_MouseDown; - RegionClickCanvas.MouseMove -= RegionClickCanvas_MouseMove; - RegionClickCanvas.MouseUp -= RegionClickCanvas_MouseUp; - - KeyDown -= FullscreenGrab_KeyDown; - KeyUp -= FullscreenGrab_KeyUp; - } - - #endregion - - #region Mouse Events - - private void RegionClickCanvas_MouseDown(object sender, MouseButtonEventArgs e) - { - if (e.RightButton == MouseButtonState.Pressed) - return; - - isSelecting = true; - RegionClickCanvas.CaptureMouse(); - CursorClipper.ClipCursor(this); - clickedPoint = e.GetPosition(this); - selectBorder.Height = 1; - selectBorder.Width = 1; - - dpiScale = VisualTreeHelper.GetDpi(this); - - try - { - RegionClickCanvas.Children.Remove(selectBorder); - } - catch (Exception) - { - // ignored - } - - selectBorder.BorderThickness = new Thickness(2); - System.Windows.Media.Color borderColor = System.Windows.Media.Color.FromArgb(255, 40, 118, 126); - selectBorder.BorderBrush = new SolidColorBrush(borderColor); - _ = RegionClickCanvas.Children.Add(selectBorder); - Canvas.SetLeft(selectBorder, clickedPoint.X); - Canvas.SetTop(selectBorder, clickedPoint.Y); - - DisplayInfo[] screens = DisplayInfo.AllDisplayInfos; - System.Windows.Point formsPoint = new((int)clickedPoint.X, (int)clickedPoint.Y); - foreach (DisplayInfo scr in screens) - { - Rect bound = scr.ScaledBounds(); - if (bound.Contains(formsPoint)) - CurrentScreen = scr; - } - } - - private void RegionClickCanvas_MouseMove(object sender, MouseEventArgs e) - { - if (!isSelecting) - return; - - Point movingPoint = e.GetPosition(this); - - if (Keyboard.Modifiers == ModifierKeys.Shift) - { - PanSelection(movingPoint); - return; - } - - isShiftDown = false; - - double left = Math.Min(clickedPoint.X, movingPoint.X); - double top = Math.Min(clickedPoint.Y, movingPoint.Y); - - selectBorder.Height = Math.Max(clickedPoint.Y, movingPoint.Y) - top; - selectBorder.Width = Math.Max(clickedPoint.X, movingPoint.X) - left; - selectBorder.Height += 2; - selectBorder.Width += 2; - - clippingGeometry.Rect = new Rect( - new System.Windows.Point(left, top), - new System.Windows.Size(selectBorder.Width - 2, selectBorder.Height - 2)); - Canvas.SetLeft(selectBorder, left - 1); - Canvas.SetTop(selectBorder, top - 1); - } - - private void RegionClickCanvas_MouseUp(object sender, MouseButtonEventArgs e) - { - if (!isSelecting) - return; - - isSelecting = false; - CurrentScreen = null; - CursorClipper.UnClipCursor(); - RegionClickCanvas.ReleaseMouseCapture(); - clippingGeometry.Rect = new Rect( - new System.Windows.Point(0, 0), - new System.Windows.Size(0, 0)); - - System.Windows.Point movingPoint = e.GetPosition(this); - Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice; - movingPoint.X *= m.M11; - movingPoint.Y *= m.M22; - - movingPoint.X = Math.Round(movingPoint.X); - movingPoint.Y = Math.Round(movingPoint.Y); - - double xDimScaled = Canvas.GetLeft(selectBorder) * m.M11; - double yDimScaled = Canvas.GetTop(selectBorder) * m.M22; - - Rectangle regionScaled = new( - (int)xDimScaled, - (int)yDimScaled, - (int)(selectBorder.Width * m.M11), - (int)(selectBorder.Height * m.M22)); - - try - { - RegionClickCanvas.Children.Remove(selectBorder); - } - catch - { - // ignored - } - - //FIXME: ZGGSONG - Point absPosPoint = this.GetAbsolutePosition(); - - int thisCorrectedLeft = (int)absPosPoint.X + regionScaled.Left; - int thisCorrectedTop = (int)absPosPoint.Y + regionScaled.Top; - - Rectangle correctedRegion = new(thisCorrectedLeft, thisCorrectedTop, regionScaled.Width, regionScaled.Height); - Image = ImageMethods.GetRegionOfScreenAsBitmap(correctedRegion); - - DialogResult = true; - } - - private void PanSelection(Point movingPoint) - { - if (!isShiftDown) - { - shiftPoint = movingPoint; - selectLeft = Canvas.GetLeft(selectBorder); - selectTop = Canvas.GetTop(selectBorder); - } - - isShiftDown = true; - xShiftDelta = (movingPoint.X - shiftPoint.X); - yShiftDelta = (movingPoint.Y - shiftPoint.Y); - - double leftValue = selectLeft + xShiftDelta; - double topValue = selectTop + yShiftDelta; - - if (CurrentScreen is not null && dpiScale is not null) - { - double currentScreenLeft = CurrentScreen.Bounds.Left; // Should always be 0 - double currentScreenRight = CurrentScreen.Bounds.Right / dpiScale.Value.DpiScaleX; - double currentScreenTop = CurrentScreen.Bounds.Top; // Should always be 0 - double currentScreenBottom = CurrentScreen.Bounds.Bottom / dpiScale.Value.DpiScaleY; - - leftValue = Math.Clamp(leftValue, currentScreenLeft, (currentScreenRight - selectBorder.Width)); - topValue = Math.Clamp(topValue, currentScreenTop, (currentScreenBottom - selectBorder.Height)); - } - - clippingGeometry.Rect = new Rect( - new System.Windows.Point(leftValue, topValue), - new System.Windows.Size(selectBorder.Width - 2, selectBorder.Height - 2)); - Canvas.SetLeft(selectBorder, leftValue - 1); - Canvas.SetTop(selectBorder, topValue - 1); - } - - #endregion -} \ No newline at end of file diff --git a/src/ScreenGrab/Models/NullAsyncResult.cs b/src/ScreenGrab/Models/NullAsyncResult.cs index 7f9f137..9ec7054 100644 --- a/src/ScreenGrab/Models/NullAsyncResult.cs +++ b/src/ScreenGrab/Models/NullAsyncResult.cs @@ -13,5 +13,4 @@ public class NullAsyncResult : IAsyncResult public class NullWaitHandle : WaitHandle { - } \ No newline at end of file diff --git a/src/ScreenGrab/NativeMethods.cs b/src/ScreenGrab/NativeMethods.cs index 6bb243b..666a4de 100644 --- a/src/ScreenGrab/NativeMethods.cs +++ b/src/ScreenGrab/NativeMethods.cs @@ -1,11 +1,10 @@ -using System; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; internal static partial class NativeMethods { // See http://msdn.microsoft.com/en-us/library/ms649021%28v=vs.85%29.aspx public const int WM_CLIPBOARDUPDATE = 0x031D; - public static IntPtr HWND_MESSAGE = new IntPtr(-3); + public static IntPtr HWND_MESSAGE = new(-3); // See http://msdn.microsoft.com/en-us/library/ms632599%28VS.85%29.aspx#message_only [LibraryImport("user32.dll", SetLastError = true)] diff --git a/src/ScreenGrab/OSInterop.cs b/src/ScreenGrab/OSInterop.cs index da4dc13..cd9aa76 100644 --- a/src/ScreenGrab/OSInterop.cs +++ b/src/ScreenGrab/OSInterop.cs @@ -1,18 +1,40 @@ -using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Windows.Forms; -static partial class OSInterop +internal static partial class OSInterop { + public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); + + public enum InputType : uint + { + INPUT_MOUSE, + INPUT_KEYBOARD, + INPUT_HARDWARE + } + + public const int SM_CMONITORS = 80; + + public const int WH_KEYBOARD_LL = 13; + public const int VK_SHIFT = 0x10; + public const int VK_CONTROL = 0x11; + public const int VK_MENU = 0x12; + public const int VK_LWIN = 0x5B; + public const int VK_RWIN = 0x5C; + public const int VK_ESCAPE = 0x1B; + public const int WM_HOTKEY = 0x0312; + public const int WM_KEYDOWN = 0x0100; + public const int WM_KEYUP = 0x0101; + [LibraryImport("user32.dll")] public static partial int GetSystemMetrics(int smIndex); - public const int SM_CMONITORS = 80; [LibraryImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static partial bool SystemParametersInfo(int nAction, int nParam, ref RECT rc, int nUpdate); [DllImport("user32.dll", CharSet = CharSet.Auto)] - public static extern bool GetMonitorInfo(HandleRef hmonitor, [In, Out] MONITORINFOEX info); + public static extern bool GetMonitorInfo(HandleRef hmonitor, [In] [Out] MONITORINFOEX info); [DllImport("user32.dll")] public static extern IntPtr MonitorFromWindow(HandleRef handle, int flags); @@ -22,46 +44,14 @@ static partial class OSInterop public static partial bool ClipCursor(ref RECT lpRect); [DllImport("user32.dll")] - public static extern bool ClipCursor([In()] IntPtr lpRect); - - public struct RECT - { - public int left; - public int top; - public int right; - public int bottom; - public int width { get { return right - left; } } - public int height { get { return bottom - top; } } - } - - [StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)] - public class MONITORINFOEX - { - public int cbSize = Marshal.SizeOf(typeof(MONITORINFOEX)); - public RECT rcMonitor = new RECT(); - public RECT rcWork = new RECT(); - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] - public char[] szDevice = new char[32]; - public int dwFlags; - } - - public const int WH_KEYBOARD_LL = 13; - public const int VK_SHIFT = 0x10; - public const int VK_CONTROL = 0x11; - public const int VK_MENU = 0x12; - public const int VK_LWIN = 0x5B; - public const int VK_RWIN = 0x5C; - public const int VK_ESCAPE = 0x1B; - public const int WM_HOTKEY = 0x0312; - public const int WM_KEYDOWN = 0x0100; - public const int WM_KEYUP = 0x0101; + public static extern bool ClipCursor([In] IntPtr lpRect); [LibraryImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] internal static partial bool FreeLibrary(IntPtr hModule); [LibraryImport("user32.dll", SetLastError = true)] - [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })] + [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvStdcall) })] [return: MarshalAs(UnmanagedType.Bool)] internal static partial bool UnhookWindowsHookEx(IntPtr idHook); @@ -69,53 +59,61 @@ public class MONITORINFOEX internal static partial IntPtr LoadLibrary(string lpFileName); [LibraryImport("user32.dll", SetLastError = true)] - [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })] + [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvStdcall) })] internal static partial IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId); [LibraryImport("user32.dll", SetLastError = true)] - [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })] + [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvStdcall) })] internal static partial IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam); [LibraryImport("user32.dll", SetLastError = true)] - [UnmanagedCallConv(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })] + [UnmanagedCallConv(CallConvs = new[] { typeof(CallConvStdcall) })] internal static partial short GetAsyncKeyState(int vKey); - public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); - [LibraryImport("user32.dll")] - public static partial short GetAsyncKeyState(System.Windows.Forms.Keys vKey); + public static partial short GetAsyncKeyState(Keys vKey); [LibraryImport("user32.dll")] public static partial uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize); + public struct RECT + { + public int left; + public int top; + public int right; + public int bottom; + public int width => right - left; + public int height => bottom - top; + } + + [StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Auto)] + public class MONITORINFOEX + { + public int cbSize = Marshal.SizeOf(typeof(MONITORINFOEX)); + public RECT rcMonitor = new(); + public RECT rcWork = new(); + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] + public char[] szDevice = new char[32]; + + public int dwFlags; + } + [StructLayout(LayoutKind.Sequential)] public struct INPUT { public InputType Type; public InputUnion U; - public static int Size - { - get { return Marshal.SizeOf(typeof(INPUT)); } - } - } - - public enum InputType : uint - { - INPUT_MOUSE, - INPUT_KEYBOARD, - INPUT_HARDWARE, + public static int Size => Marshal.SizeOf(typeof(INPUT)); } [StructLayout(LayoutKind.Explicit)] public struct InputUnion { - [FieldOffset(0)] - internal MOUSEINPUT Mi; - [FieldOffset(0)] - internal KEYBDINPUT Ki; - [FieldOffset(0)] - internal HARDWAREINPUT Hi; + [FieldOffset(0)] internal MOUSEINPUT Mi; + [FieldOffset(0)] internal KEYBDINPUT Ki; + [FieldOffset(0)] internal HARDWAREINPUT Hi; } [StructLayout(LayoutKind.Sequential)] @@ -145,7 +143,7 @@ internal enum MOUSEEVENTF : uint VIRTUALDESK = 0x4000, WHEEL = 0x0800, XDOWN = 0x0080, - XUP = 0x0100, + XUP = 0x0100 } [StructLayout(LayoutKind.Sequential)] @@ -164,872 +162,873 @@ internal enum KEYEVENTF : uint EXTENDEDKEY = 0x0001, KEYUP = 0x0002, SCANCODE = 0x0008, - UNICODE = 0x0004, + UNICODE = 0x0004 } internal enum VirtualKeyShort : short { /// - /// Left mouse button + /// Left mouse button /// LBUTTON = 0x01, /// - /// Right mouse button + /// Right mouse button /// RBUTTON = 0x02, /// - /// Control-break processing + /// Control-break processing /// CANCEL = 0x03, /// - /// Middle mouse button (three-button mouse) + /// Middle mouse button (three-button mouse) /// MBUTTON = 0x04, /// - /// Windows 2000/XP: X1 mouse button + /// Windows 2000/XP: X1 mouse button /// XBUTTON1 = 0x05, /// - /// Windows 2000/XP: X2 mouse button + /// Windows 2000/XP: X2 mouse button /// XBUTTON2 = 0x06, /// - /// BACKSPACE key + /// BACKSPACE key /// BACK = 0x08, /// - /// TAB key + /// TAB key /// TAB = 0x09, /// - /// CLEAR key + /// CLEAR key /// CLEAR = 0x0C, /// - /// ENTER key + /// ENTER key /// RETURN = 0x0D, /// - /// SHIFT key + /// SHIFT key /// SHIFT = 0x10, /// - /// CTRL key + /// CTRL key /// CONTROL = 0x11, /// - /// ALT key + /// ALT key /// MENU = 0x12, /// - /// PAUSE key + /// PAUSE key /// PAUSE = 0x13, /// - /// CAPS LOCK key + /// CAPS LOCK key /// CAPITAL = 0x14, /// - /// Input Method Editor (IME) Kana mode + /// Input Method Editor (IME) Kana mode /// KANA = 0x15, /// - /// IME Hangul mode + /// IME Hangul mode /// HANGUL = 0x15, /// - /// IME Junja mode + /// IME Junja mode /// JUNJA = 0x17, /// - /// IME final mode + /// IME final mode /// FINAL = 0x18, /// - /// IME Hanja mode + /// IME Hanja mode /// HANJA = 0x19, /// - /// IME Kanji mode + /// IME Kanji mode /// KANJI = 0x19, /// - /// ESC key + /// ESC key /// ESCAPE = 0x1B, /// - /// IME convert + /// IME convert /// CONVERT = 0x1C, /// - /// IME nonconvert + /// IME nonconvert /// NONCONVERT = 0x1D, /// - /// IME accept + /// IME accept /// ACCEPT = 0x1E, /// - /// IME mode change request + /// IME mode change request /// MODECHANGE = 0x1F, /// - /// SPACEBAR + /// SPACEBAR /// SPACE = 0x20, /// - /// PAGE UP key + /// PAGE UP key /// PRIOR = 0x21, /// - /// PAGE DOWN key + /// PAGE DOWN key /// NEXT = 0x22, /// - /// END key + /// END key /// END = 0x23, /// - /// HOME key + /// HOME key /// HOME = 0x24, /// - /// LEFT ARROW key + /// LEFT ARROW key /// LEFT = 0x25, /// - /// UP ARROW key + /// UP ARROW key /// UP = 0x26, /// - /// RIGHT ARROW key + /// RIGHT ARROW key /// RIGHT = 0x27, /// - /// DOWN ARROW key + /// DOWN ARROW key /// DOWN = 0x28, /// - /// SELECT key + /// SELECT key /// SELECT = 0x29, /// - /// PRINT key + /// PRINT key /// PRINT = 0x2A, /// - /// EXECUTE key + /// EXECUTE key /// EXECUTE = 0x2B, /// - /// PRINT SCREEN key + /// PRINT SCREEN key /// SNAPSHOT = 0x2C, /// - /// INS key + /// INS key /// INSERT = 0x2D, /// - /// DEL key + /// DEL key /// DELETE = 0x2E, /// - /// HELP key + /// HELP key /// HELP = 0x2F, /// - /// 0 key + /// 0 key /// KEY_0 = 0x30, /// - /// 1 key + /// 1 key /// KEY_1 = 0x31, /// - /// 2 key + /// 2 key /// KEY_2 = 0x32, /// - /// 3 key + /// 3 key /// KEY_3 = 0x33, /// - /// 4 key + /// 4 key /// KEY_4 = 0x34, /// - /// 5 key + /// 5 key /// KEY_5 = 0x35, /// - /// 6 key + /// 6 key /// KEY_6 = 0x36, /// - /// 7 key + /// 7 key /// KEY_7 = 0x37, /// - /// 8 key + /// 8 key /// KEY_8 = 0x38, /// - /// 9 key + /// 9 key /// KEY_9 = 0x39, /// - /// A key + /// A key /// KEY_A = 0x41, /// - /// B key + /// B key /// KEY_B = 0x42, /// - /// C key + /// C key /// KEY_C = 0x43, /// - /// D key + /// D key /// KEY_D = 0x44, /// - /// E key + /// E key /// KEY_E = 0x45, /// - /// F key + /// F key /// KEY_F = 0x46, /// - /// G key + /// G key /// KEY_G = 0x47, /// - /// H key + /// H key /// KEY_H = 0x48, /// - /// I key + /// I key /// KEY_I = 0x49, /// - /// J key + /// J key /// KEY_J = 0x4A, /// - /// K key + /// K key /// KEY_K = 0x4B, /// - /// L key + /// L key /// KEY_L = 0x4C, /// - /// M key + /// M key /// KEY_M = 0x4D, /// - /// N key + /// N key /// KEY_N = 0x4E, /// - /// O key + /// O key /// KEY_O = 0x4F, /// - /// P key + /// P key /// KEY_P = 0x50, /// - /// Q key + /// Q key /// KEY_Q = 0x51, /// - /// R key + /// R key /// KEY_R = 0x52, /// - /// S key + /// S key /// KEY_S = 0x53, /// - /// T key + /// T key /// KEY_T = 0x54, /// - /// U key + /// U key /// KEY_U = 0x55, /// - /// V key + /// V key /// KEY_V = 0x56, /// - /// W key + /// W key /// KEY_W = 0x57, /// - /// X key + /// X key /// KEY_X = 0x58, /// - /// Y key + /// Y key /// KEY_Y = 0x59, /// - /// Z key + /// Z key /// KEY_Z = 0x5A, /// - /// Left Windows key (Microsoft Natural keyboard) + /// Left Windows key (Microsoft Natural keyboard) /// LWIN = 0x5B, /// - /// Right Windows key (Natural keyboard) + /// Right Windows key (Natural keyboard) /// RWIN = 0x5C, /// - /// Applications key (Natural keyboard) + /// Applications key (Natural keyboard) /// APPS = 0x5D, /// - /// Computer Sleep key + /// Computer Sleep key /// SLEEP = 0x5F, /// - /// Numeric keypad 0 key + /// Numeric keypad 0 key /// NUMPAD0 = 0x60, /// - /// Numeric keypad 1 key + /// Numeric keypad 1 key /// NUMPAD1 = 0x61, /// - /// Numeric keypad 2 key + /// Numeric keypad 2 key /// NUMPAD2 = 0x62, /// - /// Numeric keypad 3 key + /// Numeric keypad 3 key /// NUMPAD3 = 0x63, /// - /// Numeric keypad 4 key + /// Numeric keypad 4 key /// NUMPAD4 = 0x64, /// - /// Numeric keypad 5 key + /// Numeric keypad 5 key /// NUMPAD5 = 0x65, /// - /// Numeric keypad 6 key + /// Numeric keypad 6 key /// NUMPAD6 = 0x66, /// - /// Numeric keypad 7 key + /// Numeric keypad 7 key /// NUMPAD7 = 0x67, /// - /// Numeric keypad 8 key + /// Numeric keypad 8 key /// NUMPAD8 = 0x68, /// - /// Numeric keypad 9 key + /// Numeric keypad 9 key /// NUMPAD9 = 0x69, /// - /// Multiply key + /// Multiply key /// MULTIPLY = 0x6A, /// - /// Add key + /// Add key /// ADD = 0x6B, /// - /// Separator key + /// Separator key /// SEPARATOR = 0x6C, /// - /// Subtract key + /// Subtract key /// SUBTRACT = 0x6D, /// - /// Decimal key + /// Decimal key /// DECIMAL = 0x6E, /// - /// Divide key + /// Divide key /// DIVIDE = 0x6F, /// - /// F1 key + /// F1 key /// F1 = 0x70, /// - /// F2 key + /// F2 key /// F2 = 0x71, /// - /// F3 key + /// F3 key /// F3 = 0x72, /// - /// F4 key + /// F4 key /// F4 = 0x73, /// - /// F5 key + /// F5 key /// F5 = 0x74, /// - /// F6 key + /// F6 key /// F6 = 0x75, /// - /// F7 key + /// F7 key /// F7 = 0x76, /// - /// F8 key + /// F8 key /// F8 = 0x77, /// - /// F9 key + /// F9 key /// F9 = 0x78, /// - /// F10 key + /// F10 key /// F10 = 0x79, /// - /// F11 key + /// F11 key /// F11 = 0x7A, /// - /// F12 key + /// F12 key /// F12 = 0x7B, /// - /// F13 key + /// F13 key /// F13 = 0x7C, /// - /// F14 key + /// F14 key /// F14 = 0x7D, /// - /// F15 key + /// F15 key /// F15 = 0x7E, /// - /// F16 key + /// F16 key /// F16 = 0x7F, /// - /// F17 key + /// F17 key /// F17 = 0x80, /// - /// F18 key + /// F18 key /// F18 = 0x81, /// - /// F19 key + /// F19 key /// F19 = 0x82, /// - /// F20 key + /// F20 key /// F20 = 0x83, /// - /// F21 key + /// F21 key /// F21 = 0x84, /// - /// F22 key, (PPC only) Key used to lock device. + /// F22 key, (PPC only) Key used to lock device. /// F22 = 0x85, /// - /// F23 key + /// F23 key /// F23 = 0x86, /// - /// F24 key + /// F24 key /// F24 = 0x87, /// - /// NUM LOCK key + /// NUM LOCK key /// NUMLOCK = 0x90, /// - /// SCROLL LOCK key + /// SCROLL LOCK key /// SCROLL = 0x91, /// - /// Left SHIFT key + /// Left SHIFT key /// LSHIFT = 0xA0, /// - /// Right SHIFT key + /// Right SHIFT key /// RSHIFT = 0xA1, /// - /// Left CONTROL key + /// Left CONTROL key /// LCONTROL = 0xA2, /// - /// Right CONTROL key + /// Right CONTROL key /// RCONTROL = 0xA3, /// - /// Left MENU key + /// Left MENU key /// LMENU = 0xA4, /// - /// Right MENU key + /// Right MENU key /// RMENU = 0xA5, /// - /// Windows 2000/XP: Browser Back key + /// Windows 2000/XP: Browser Back key /// BROWSER_BACK = 0xA6, /// - /// Windows 2000/XP: Browser Forward key + /// Windows 2000/XP: Browser Forward key /// BROWSER_FORWARD = 0xA7, /// - /// Windows 2000/XP: Browser Refresh key + /// Windows 2000/XP: Browser Refresh key /// BROWSER_REFRESH = 0xA8, /// - /// Windows 2000/XP: Browser Stop key + /// Windows 2000/XP: Browser Stop key /// BROWSER_STOP = 0xA9, /// - /// Windows 2000/XP: Browser Search key + /// Windows 2000/XP: Browser Search key /// BROWSER_SEARCH = 0xAA, /// - /// Windows 2000/XP: Browser Favorites key + /// Windows 2000/XP: Browser Favorites key /// BROWSER_FAVORITES = 0xAB, /// - /// Windows 2000/XP: Browser Start and Home key + /// Windows 2000/XP: Browser Start and Home key /// BROWSER_HOME = 0xAC, /// - /// Windows 2000/XP: Volume Mute key + /// Windows 2000/XP: Volume Mute key /// VOLUME_MUTE = 0xAD, /// - /// Windows 2000/XP: Volume Down key + /// Windows 2000/XP: Volume Down key /// VOLUME_DOWN = 0xAE, /// - /// Windows 2000/XP: Volume Up key + /// Windows 2000/XP: Volume Up key /// VOLUME_UP = 0xAF, /// - /// Windows 2000/XP: Next Track key + /// Windows 2000/XP: Next Track key /// MEDIA_NEXT_TRACK = 0xB0, /// - /// Windows 2000/XP: Previous Track key + /// Windows 2000/XP: Previous Track key /// MEDIA_PREV_TRACK = 0xB1, /// - /// Windows 2000/XP: Stop Media key + /// Windows 2000/XP: Stop Media key /// MEDIA_STOP = 0xB2, /// - /// Windows 2000/XP: Play/Pause Media key + /// Windows 2000/XP: Play/Pause Media key /// MEDIA_PLAY_PAUSE = 0xB3, /// - /// Windows 2000/XP: Start Mail key + /// Windows 2000/XP: Start Mail key /// LAUNCH_MAIL = 0xB4, /// - /// Windows 2000/XP: Select Media key + /// Windows 2000/XP: Select Media key /// LAUNCH_MEDIA_SELECT = 0xB5, /// - /// Windows 2000/XP: Start Application 1 key + /// Windows 2000/XP: Start Application 1 key /// LAUNCH_APP1 = 0xB6, /// - /// Windows 2000/XP: Start Application 2 key + /// Windows 2000/XP: Start Application 2 key /// LAUNCH_APP2 = 0xB7, /// - /// Used for miscellaneous characters; it can vary by keyboard. + /// Used for miscellaneous characters; it can vary by keyboard. /// OEM_1 = 0xBA, /// - /// Windows 2000/XP: For any country/region, the '+' key + /// Windows 2000/XP: For any country/region, the '+' key /// OEM_PLUS = 0xBB, /// - /// Windows 2000/XP: For any country/region, the ',' key + /// Windows 2000/XP: For any country/region, the ',' key /// OEM_COMMA = 0xBC, /// - /// Windows 2000/XP: For any country/region, the '-' key + /// Windows 2000/XP: For any country/region, the '-' key /// OEM_MINUS = 0xBD, /// - /// Windows 2000/XP: For any country/region, the '.' key + /// Windows 2000/XP: For any country/region, the '.' key /// OEM_PERIOD = 0xBE, /// - /// Used for miscellaneous characters; it can vary by keyboard. + /// Used for miscellaneous characters; it can vary by keyboard. /// OEM_2 = 0xBF, /// - /// Used for miscellaneous characters; it can vary by keyboard. + /// Used for miscellaneous characters; it can vary by keyboard. /// OEM_3 = 0xC0, /// - /// Used for miscellaneous characters; it can vary by keyboard. + /// Used for miscellaneous characters; it can vary by keyboard. /// OEM_4 = 0xDB, /// - /// Used for miscellaneous characters; it can vary by keyboard. + /// Used for miscellaneous characters; it can vary by keyboard. /// OEM_5 = 0xDC, /// - /// Used for miscellaneous characters; it can vary by keyboard. + /// Used for miscellaneous characters; it can vary by keyboard. /// OEM_6 = 0xDD, /// - /// Used for miscellaneous characters; it can vary by keyboard. + /// Used for miscellaneous characters; it can vary by keyboard. /// OEM_7 = 0xDE, /// - /// Used for miscellaneous characters; it can vary by keyboard. + /// Used for miscellaneous characters; it can vary by keyboard. /// OEM_8 = 0xDF, /// - /// Windows 2000/XP: Either the angle bracket key or the backslash key on the RT 102-key keyboard + /// Windows 2000/XP: Either the angle bracket key or the backslash key on the RT 102-key keyboard /// OEM_102 = 0xE2, /// - /// Windows 95/98/Me, Windows NT 4.0, Windows 2000/XP: IME PROCESS key + /// Windows 95/98/Me, Windows NT 4.0, Windows 2000/XP: IME PROCESS key /// PROCESSKEY = 0xE5, /// - /// Windows 2000/XP: Used to pass Unicode characters as if they were keystrokes. - /// The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more information, - /// see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP + /// Windows 2000/XP: Used to pass Unicode characters as if they were keystrokes. + /// The VK_PACKET key is the low word of a 32-bit Virtual Key value used for non-keyboard input methods. For more + /// information, + /// see Remark in KEYBDINPUT, SendInput, WM_KEYDOWN, and WM_KEYUP /// PACKET = 0xE7, /// - /// Attn key + /// Attn key /// ATTN = 0xF6, /// - /// CrSel key + /// CrSel key /// CRSEL = 0xF7, /// - /// ExSel key + /// ExSel key /// EXSEL = 0xF8, /// - /// Erase EOF key + /// Erase EOF key /// EREOF = 0xF9, /// - /// Play key + /// Play key /// PLAY = 0xFA, /// - /// Zoom key + /// Zoom key /// ZOOM = 0xFB, /// - /// Reserved + /// Reserved /// NONAME = 0xFC, /// - /// PA1 key + /// PA1 key /// PA1 = 0xFD, /// - /// Clear key + /// Clear key /// - OEM_CLEAR = 0xFE, + OEM_CLEAR = 0xFE } internal enum ScanCodeShort : short @@ -1205,7 +1204,7 @@ internal enum ScanCodeShort : short ZOOM = 98, NONAME = 0, PA1 = 0, - OEM_CLEAR = 0, + OEM_CLEAR = 0 } [StructLayout(LayoutKind.Sequential)] @@ -1220,27 +1219,30 @@ internal struct HARDWAREINPUT internal struct LowLevelKeyboardInputEvent { /// - /// A virtual-key code. The code must be a value in the range 1 to 254. + /// A virtual-key code. The code must be a value in the range 1 to 254. /// public int VirtualCode; /// - /// A hardware scan code for the key. + /// A hardware scan code for the key. /// public int HardwareScanCode; /// - /// The extended-key flag, event-injected Flags, context code, and transition-state flag. This member is specified as follows. An application can use the following values to test the keystroke Flags. Testing LLKHF_INJECTED (bit 4) will tell you whether the event was injected. If it was, then testing LLKHF_LOWER_IL_INJECTED (bit 1) will tell you whether or not the event was injected from a process running at lower integrity level. + /// The extended-key flag, event-injected Flags, context code, and transition-state flag. This member is specified as + /// follows. An application can use the following values to test the keystroke Flags. Testing LLKHF_INJECTED (bit 4) + /// will tell you whether the event was injected. If it was, then testing LLKHF_LOWER_IL_INJECTED (bit 1) will tell you + /// whether or not the event was injected from a process running at lower integrity level. /// public int Flags; /// - /// The time stamp for this message, equivalent to what GetMessageTime would return for this message. + /// The time stamp for this message, equivalent to what GetMessageTime would return for this message. /// public int TimeStamp; /// - /// Additional information associated with the message. + /// Additional information associated with the message. /// public IntPtr AdditionalInformation; } diff --git a/src/ScreenGrab/ScreenGrab.csproj b/src/ScreenGrab/ScreenGrab.csproj index 96df6cc..bb4e439 100644 --- a/src/ScreenGrab/ScreenGrab.csproj +++ b/src/ScreenGrab/ScreenGrab.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/ScreenGrab/ScreenGrabView.xaml b/src/ScreenGrab/ScreenGrabView.xaml new file mode 100644 index 0000000..141a740 --- /dev/null +++ b/src/ScreenGrab/ScreenGrabView.xaml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ScreenGrab/ScreenGrabView.xaml.cs b/src/ScreenGrab/ScreenGrabView.xaml.cs new file mode 100644 index 0000000..2b3dd6a --- /dev/null +++ b/src/ScreenGrab/ScreenGrabView.xaml.cs @@ -0,0 +1,314 @@ +using System.Drawing; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using Dapplo.Windows.User32; +using ScreenGrab.Extensions; +using ScreenGrab.Utilities; +using Color = System.Windows.Media.Color; +using Point = System.Windows.Point; +using Size = System.Windows.Size; + +namespace ScreenGrab; + +public partial class ScreenGrabView +{ + #region Constructors + + public ScreenGrabView(Action? action) + { + InitializeComponent(); + _onImageCaptured = action; + } + + #endregion Constructors + + #region Properties + + private DisplayInfo? CurrentScreen { get; set; } + + #endregion Properties + + #region Fields + + private Point _clickedPoint; + private DpiScale? _dpiScale; + private bool _isSelecting; + private bool _isShiftDown; + private readonly Border _selectBorder = new(); + private double _selectLeft; + private double _selectTop; + private Point _shiftPoint; + private double _xShiftDelta; + private double _yShiftDelta; + + private readonly Action? _onImageCaptured; + + #endregion Fields + + #region Window Events + + private void SetImageToBackground() + { + BackgroundImage.Source = null; + BackgroundImage.Source = ImageMethods.GetWindowBoundsImage(this); + BackgroundBrush.Opacity = 0.2; + } + + private async void FreezeUnfreeze() + { + if (BackgroundImage.Source == null) + { + BackgroundBrush.Opacity = 0; + await Task.Delay(150); + SetImageToBackground(); + } + else + { + BackgroundImage.Source = null; + } + } + + private void CloseAllFullscreenGrabs() + { + foreach (var window in Application.Current.Windows) + if (window is ScreenGrabView sgv) + sgv.Close(); + } + + private void FreezeUnfreezeAllScreenGrabs() + { + + foreach (var window in Application.Current.Windows) + if (window is ScreenGrabView sgv) + sgv.FreezeUnfreeze(); + } + + private void ScreenGrab_KeyDown(object sender, KeyEventArgs e) + { + switch (e.Key) + { + case Key.Escape: + CloseAllFullscreenGrabs(); + break; + case Key.F: + FreezeUnfreezeAllScreenGrabs(); + break; + } + } + + private void ScreenGrab_KeyUp(object sender, KeyEventArgs e) + { + switch (e.Key) + { + case Key.LeftShift: + case Key.RightShift: + _isShiftDown = false; + _clickedPoint = new Point(_clickedPoint.X + _xShiftDelta, _clickedPoint.Y + _yShiftDelta); + break; + } + } + + private void Window_Closed(object? sender, EventArgs e) + { + Close(); + + GC.Collect(); + } + + private void Window_Loaded(object sender, RoutedEventArgs e) + { + WindowState = WindowState.Maximized; + FullWindow.Rect = new Rect(0, 0, Width, Height); + KeyDown += ScreenGrab_KeyDown; + KeyUp += ScreenGrab_KeyUp; + + SetImageToBackground(); + +#if DEBUG + Topmost = false; +#endif + } + + private void Window_Unloaded(object sender, RoutedEventArgs e) + { + BackgroundImage.Source = null; + BackgroundImage.UpdateLayout(); + CurrentScreen = null; + _dpiScale = null; + + Loaded -= Window_Loaded; + Unloaded -= Window_Unloaded; + + RegionClickCanvas.MouseDown -= RegionClickCanvas_MouseDown; + RegionClickCanvas.MouseMove -= RegionClickCanvas_MouseMove; + RegionClickCanvas.MouseUp -= RegionClickCanvas_MouseUp; + + KeyDown -= ScreenGrab_KeyDown; + KeyUp -= ScreenGrab_KeyUp; + } + + #endregion + + #region Mouse Events + + private void RegionClickCanvas_MouseDown(object sender, MouseButtonEventArgs e) + { + // Right click to close + if (e.RightButton == MouseButtonState.Pressed) + { + CloseAllFullscreenGrabs(); + return; + } + + _isSelecting = true; + RegionClickCanvas.CaptureMouse(); + CursorClipper.ClipCursor(this); + _clickedPoint = e.GetPosition(this); + _selectBorder.Height = 1; + _selectBorder.Width = 1; + + _dpiScale = VisualTreeHelper.GetDpi(this); + + try + { + RegionClickCanvas.Children.Remove(_selectBorder); + } + catch (Exception) + { + // ignored + } + + _selectBorder.BorderThickness = new Thickness(2); + var borderColor = Color.FromArgb(255, 175, 103, 193); + _selectBorder.BorderBrush = new SolidColorBrush(borderColor); + _ = RegionClickCanvas.Children.Add(_selectBorder); + Canvas.SetLeft(_selectBorder, _clickedPoint.X); + Canvas.SetTop(_selectBorder, _clickedPoint.Y); + + DisplayInfo[] screens = DisplayInfo.AllDisplayInfos; + Point formsPoint = new((int)_clickedPoint.X, (int)_clickedPoint.Y); + foreach (var scr in screens) + { + var bound = scr.ScaledBounds(); + if (bound.Contains(formsPoint)) + CurrentScreen = scr; + } + } + + private void RegionClickCanvas_MouseMove(object sender, MouseEventArgs e) + { + if (!_isSelecting) + return; + + var movingPoint = e.GetPosition(this); + + if (Keyboard.Modifiers == ModifierKeys.Shift) + { + PanSelection(movingPoint); + return; + } + + _isShiftDown = false; + + var left = Math.Min(_clickedPoint.X, movingPoint.X); + var top = Math.Min(_clickedPoint.Y, movingPoint.Y); + + _selectBorder.Height = Math.Max(_clickedPoint.Y, movingPoint.Y) - top; + _selectBorder.Width = Math.Max(_clickedPoint.X, movingPoint.X) - left; + _selectBorder.Height += 2; + _selectBorder.Width += 2; + + ClippingGeometry.Rect = new Rect( + new Point(left, top), + new Size(_selectBorder.Width - 2, _selectBorder.Height - 2)); + Canvas.SetLeft(_selectBorder, left - 1); + Canvas.SetTop(_selectBorder, top - 1); + } + + private void RegionClickCanvas_MouseUp(object sender, MouseButtonEventArgs e) + { + if (!_isSelecting) + return; + + _isSelecting = false; + CurrentScreen = null; + CursorClipper.UnClipCursor(); + RegionClickCanvas.ReleaseMouseCapture(); + ClippingGeometry.Rect = new Rect(new Point(0, 0), new Size(0, 0)); + + var movingPoint = e.GetPosition(this); + var m = PresentationSource.FromVisual(this)!.CompositionTarget!.TransformToDevice; + movingPoint.X *= m.M11; + movingPoint.Y *= m.M22; + + movingPoint.X = Math.Round(movingPoint.X); + movingPoint.Y = Math.Round(movingPoint.Y); + + var xDimScaled = Canvas.GetLeft(_selectBorder) * m.M11; + var yDimScaled = Canvas.GetTop(_selectBorder) * m.M22; + + Rectangle regionScaled = new( + (int)xDimScaled, + (int)yDimScaled, + (int)(_selectBorder.Width * m.M11), + (int)(_selectBorder.Height * m.M22)); + + try + { + RegionClickCanvas.Children.Remove(_selectBorder); + } + catch + { + // ignored + } + + var absPosPoint = this.GetAbsolutePosition(); + + var thisCorrectedLeft = (int)absPosPoint.X + regionScaled.Left; + var thisCorrectedTop = (int)absPosPoint.Y + regionScaled.Top; + + Rectangle correctedRegion = new(thisCorrectedLeft, thisCorrectedTop, regionScaled.Width, regionScaled.Height); + var bitmap = correctedRegion.GetRegionOfScreenAsBitmap(); + _onImageCaptured?.Invoke(bitmap); + + CloseAllFullscreenGrabs(); + } + + private void PanSelection(Point movingPoint) + { + if (!_isShiftDown) + { + _shiftPoint = movingPoint; + _selectLeft = Canvas.GetLeft(_selectBorder); + _selectTop = Canvas.GetTop(_selectBorder); + } + + _isShiftDown = true; + _xShiftDelta = movingPoint.X - _shiftPoint.X; + _yShiftDelta = movingPoint.Y - _shiftPoint.Y; + + var leftValue = _selectLeft + _xShiftDelta; + var topValue = _selectTop + _yShiftDelta; + + if (CurrentScreen is not null && _dpiScale is not null) + { + double currentScreenLeft = CurrentScreen.Bounds.Left; // Should always be 0 + var currentScreenRight = CurrentScreen.Bounds.Right / _dpiScale.Value.DpiScaleX; + double currentScreenTop = CurrentScreen.Bounds.Top; // Should always be 0 + var currentScreenBottom = CurrentScreen.Bounds.Bottom / _dpiScale.Value.DpiScaleY; + + leftValue = Math.Clamp(leftValue, currentScreenLeft, currentScreenRight - _selectBorder.Width); + topValue = Math.Clamp(topValue, currentScreenTop, currentScreenBottom - _selectBorder.Height); + } + + ClippingGeometry.Rect = new Rect( + new Point(leftValue, topValue), + new Size(_selectBorder.Width - 2, _selectBorder.Height - 2)); + Canvas.SetLeft(_selectBorder, leftValue - 1); + Canvas.SetTop(_selectBorder, topValue - 1); + } + + #endregion +} \ No newline at end of file diff --git a/src/ScreenGrab/Utilities/CursorClipper.cs b/src/ScreenGrab/Utilities/CursorClipper.cs index 959e0b9..fb7357f 100644 --- a/src/ScreenGrab/Utilities/CursorClipper.cs +++ b/src/ScreenGrab/Utilities/CursorClipper.cs @@ -3,12 +3,12 @@ namespace ScreenGrab.Utilities; /// -/// Functions to constrain the mouse cursor (typically used when dragging) +/// Functions to constrain the mouse cursor (typically used when dragging) /// public static class CursorClipper { /// - /// Constrain mouse cursor to the area of the specified UI element. + /// Constrain mouse cursor to the area of the specified UI element. /// /// Target UI element. /// True on success. @@ -18,19 +18,16 @@ public static bool ClipCursor(FrameworkElement element) var topLeft = element.PointToScreen(new Point(0, 0)); - PresentationSource source = PresentationSource.FromVisual(element); - if (source?.CompositionTarget == null) - { - return false; - } + var source = PresentationSource.FromVisual(element); + if (source?.CompositionTarget == null) return false; - double dpiX = dpi96 * source.CompositionTarget.TransformToDevice.M11; - double dpiY = dpi96 * source.CompositionTarget.TransformToDevice.M22; + var dpiX = dpi96 * source.CompositionTarget.TransformToDevice.M11; + var dpiY = dpi96 * source.CompositionTarget.TransformToDevice.M22; var width = (int)((element.ActualWidth + 1) * dpiX / dpi96); var height = (int)((element.ActualHeight + 1) * dpiY / dpi96); - OSInterop.RECT rect = new OSInterop.RECT + var rect = new OSInterop.RECT { left = (int)topLeft.X, top = (int)topLeft.Y, @@ -42,7 +39,7 @@ public static bool ClipCursor(FrameworkElement element) } /// - /// Remove any mouse cursor constraint. + /// Remove any mouse cursor constraint. /// /// True on success. public static bool UnClipCursor() diff --git a/src/ScreenGrab/Utilities/ImageMethods.cs b/src/ScreenGrab/Utilities/ImageMethods.cs index 9423df6..ec69970 100644 --- a/src/ScreenGrab/Utilities/ImageMethods.cs +++ b/src/ScreenGrab/Utilities/ImageMethods.cs @@ -5,37 +5,39 @@ using System.Windows.Media; using System.Windows.Media.Imaging; using ScreenGrab.Extensions; +using PixelFormat = System.Drawing.Imaging.PixelFormat; namespace ScreenGrab.Utilities; public static class ImageMethods { - public static ImageSource GetWindowBoundsImage(Window passedWindow) + public static ImageSource? GetWindowBoundsImage(Window passedWindow) { - Bitmap bmp = GetWindowsBoundsBitmap(passedWindow); - return BitmapToImageSource(bmp); + var bmp = passedWindow.GetWindowsBoundsBitmap(); + return bmp.ToImageSource(); } - public static Bitmap GetWindowsBoundsBitmap(Window passedWindow) + public static Bitmap GetWindowsBoundsBitmap(this Window passedWindow) { - DpiScale dpi = VisualTreeHelper.GetDpi(passedWindow); - int windowWidth = (int)(passedWindow.ActualWidth * dpi.DpiScaleX); - int windowHeight = (int)(passedWindow.ActualHeight * dpi.DpiScaleY); + var dpi = VisualTreeHelper.GetDpi(passedWindow); + var windowWidth = (int)(passedWindow.ActualWidth * dpi.DpiScaleX); + var windowHeight = (int)(passedWindow.ActualHeight * dpi.DpiScaleY); var absPosPoint = passedWindow.GetAbsolutePosition(); - int thisCorrectedLeft = (int)(absPosPoint.X); - int thisCorrectedTop = (int)(absPosPoint.Y); + var thisCorrectedLeft = (int)absPosPoint.X; + var thisCorrectedTop = (int)absPosPoint.Y; - Bitmap bmp = new(windowWidth, windowHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb); - using Graphics g = Graphics.FromImage(bmp); + Bitmap bmp = new(windowWidth, windowHeight, PixelFormat.Format32bppArgb); + using var g = Graphics.FromImage(bmp); g.CopyFromScreen(thisCorrectedLeft, thisCorrectedTop, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy); return bmp; } - public static BitmapImage BitmapToImageSource(Bitmap bitmap) + public static BitmapImage? ToImageSource(this Bitmap? bitmap) { + if (bitmap == null) return default; using MemoryStream memory = new(); using WrappingStream wrapper = new(memory); @@ -54,20 +56,19 @@ public static BitmapImage BitmapToImageSource(Bitmap bitmap) return bitmapImage; } - - public static Bitmap GetRegionOfScreenAsBitmap(Rectangle region) + + public static Bitmap GetRegionOfScreenAsBitmap(this Rectangle region) { - Bitmap bmp = new(region.Width, region.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + Bitmap bmp = new(region.Width, region.Height, PixelFormat.Format32bppArgb); using var g = Graphics.FromImage(bmp); g.CopyFromScreen(region.Left, region.Top, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy); - bmp = PadImage(bmp); + bmp = bmp.PadImage(); - // Singleton.Instance.CacheLastBitmap(bmp); return bmp; } - - public static Bitmap PadImage(Bitmap image, int minW = 64, int minH = 64) + + public static Bitmap PadImage(this Bitmap image, int minW = 64, int minH = 64) { if (image.Height >= minH && image.Width >= minW) return image; diff --git a/src/ScreenGrab/Utilities/WrappingStream.cs b/src/ScreenGrab/Utilities/WrappingStream.cs index c687d27..0b47528 100644 --- a/src/ScreenGrab/Utilities/WrappingStream.cs +++ b/src/ScreenGrab/Utilities/WrappingStream.cs @@ -4,14 +4,16 @@ namespace ScreenGrab.Utilities; /// -/// A that wraps another stream. The major feature of is that it does not dispose the -/// underlying stream when it is disposed; this is useful when using classes such as and -/// that take ownership of the stream passed to their constructors. +/// A that wraps another stream. The major feature of is that it +/// does not dispose the +/// underlying stream when it is disposed; this is useful when using classes such as and +/// that take ownership of the stream passed to their +/// constructors. /// public class WrappingStream : Stream { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The wrapped stream. public WrappingStream(Stream streamBase) @@ -20,229 +22,223 @@ public WrappingStream(Stream streamBase) if (streamBase == null) throw new ArgumentNullException("streamBase"); - m_streamBase = streamBase; + WrappedStream = streamBase; } /// - /// Gets a value indicating whether the current stream supports reading. + /// Gets a value indicating whether the current stream supports reading. /// /// true if the stream supports reading; otherwise, false. - public override bool CanRead - { - get { return m_streamBase == null ? false : m_streamBase.CanRead; } - } + public override bool CanRead => WrappedStream == null ? false : WrappedStream.CanRead; /// - /// Gets a value indicating whether the current stream supports seeking. + /// Gets a value indicating whether the current stream supports seeking. /// /// true if the stream supports seeking; otherwise, false. - public override bool CanSeek - { - get { return m_streamBase == null ? false : m_streamBase.CanSeek; } - } + public override bool CanSeek => WrappedStream == null ? false : WrappedStream.CanSeek; /// - /// Gets a value indicating whether the current stream supports writing. + /// Gets a value indicating whether the current stream supports writing. /// /// true if the stream supports writing; otherwise, false. - public override bool CanWrite - { - get { return m_streamBase == null ? false : m_streamBase.CanWrite; } - } + public override bool CanWrite => WrappedStream == null ? false : WrappedStream.CanWrite; /// - /// Gets the length in bytes of the stream. + /// Gets the length in bytes of the stream. /// public override long Length { get { ThrowIfDisposed(); - if (m_streamBase is not null) - return m_streamBase.Length; + if (WrappedStream is not null) + return WrappedStream.Length; return 0; } } /// - /// Gets or sets the position within the current stream. + /// Gets or sets the position within the current stream. /// public override long Position { get { ThrowIfDisposed(); - if (m_streamBase is not null) - return m_streamBase.Position; + if (WrappedStream is not null) + return WrappedStream.Position; return 0; } set { ThrowIfDisposed(); - if (m_streamBase is not null) - m_streamBase.Position = value; + if (WrappedStream is not null) + WrappedStream.Position = value; } } /// - /// Begins an asynchronous read operation. + /// Gets the wrapped stream. + /// + /// The wrapped stream. + protected Stream? WrappedStream { get; private set; } + + /// + /// Begins an asynchronous read operation. /// public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) { ThrowIfDisposed(); - if (m_streamBase is not null && callback is not null && state is not null) - return m_streamBase.BeginRead(buffer, offset, count, callback, state); + if (WrappedStream is not null && callback is not null && state is not null) + return WrappedStream.BeginRead(buffer, offset, count, callback, state); return new NullAsyncResult(); } /// - /// Begins an asynchronous write operation. + /// Begins an asynchronous write operation. /// - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, + object? state) { ThrowIfDisposed(); - if (m_streamBase is not null && callback is not null && state is not null) - return m_streamBase.BeginWrite(buffer, offset, count, callback, state); + if (WrappedStream is not null && callback is not null && state is not null) + return WrappedStream.BeginWrite(buffer, offset, count, callback, state); return new NullAsyncResult(); } /// - /// Waits for the pending asynchronous read to complete. + /// Waits for the pending asynchronous read to complete. /// public override int EndRead(IAsyncResult asyncResult) { ThrowIfDisposed(); - if (m_streamBase is not null) - return m_streamBase.EndRead(asyncResult); + if (WrappedStream is not null) + return WrappedStream.EndRead(asyncResult); return 0; } /// - /// Ends an asynchronous write operation. + /// Ends an asynchronous write operation. /// public override void EndWrite(IAsyncResult asyncResult) { ThrowIfDisposed(); - if (m_streamBase is not null) - m_streamBase.EndWrite(asyncResult); + if (WrappedStream is not null) + WrappedStream.EndWrite(asyncResult); } /// - /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. /// public override void Flush() { ThrowIfDisposed(); - if (m_streamBase is not null) - m_streamBase.Flush(); + if (WrappedStream is not null) + WrappedStream.Flush(); } /// - /// Reads a sequence of bytes from the current stream and advances the position - /// within the stream by the number of bytes read. + /// Reads a sequence of bytes from the current stream and advances the position + /// within the stream by the number of bytes read. /// public override int Read(byte[] buffer, int offset, int count) { ThrowIfDisposed(); - if (m_streamBase is not null) - return m_streamBase.Read(buffer, offset, count); - else - return 0; + if (WrappedStream is not null) + return WrappedStream.Read(buffer, offset, count); + return 0; } /// - /// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end of the stream. + /// Reads a byte from the stream and advances the position within the stream by one byte, or returns -1 if at the end + /// of the stream. /// public override int ReadByte() { ThrowIfDisposed(); - if (m_streamBase is not null) - return m_streamBase.ReadByte(); - else - return 0; + if (WrappedStream is not null) + return WrappedStream.ReadByte(); + return 0; } /// - /// Sets the position within the current stream. + /// Sets the position within the current stream. /// - /// A byte offset relative to the parameter. - /// A value of type indicating the reference point used to obtain the new position. + /// A byte offset relative to the parameter. + /// + /// A value of type indicating the reference point used to + /// obtain the new position. + /// /// The new position within the current stream. public override long Seek(long offset, SeekOrigin origin) { ThrowIfDisposed(); - if (m_streamBase is not null) - return m_streamBase.Seek(offset, origin); - else - return 0; + if (WrappedStream is not null) + return WrappedStream.Seek(offset, origin); + return 0; } /// - /// Sets the length of the current stream. + /// Sets the length of the current stream. /// /// The desired length of the current stream in bytes. public override void SetLength(long value) { ThrowIfDisposed(); - if (m_streamBase is not null) - m_streamBase.SetLength(value); + if (WrappedStream is not null) + WrappedStream.SetLength(value); } /// - /// Writes a sequence of bytes to the current stream and advances the current position - /// within this stream by the number of bytes written. + /// Writes a sequence of bytes to the current stream and advances the current position + /// within this stream by the number of bytes written. /// public override void Write(byte[] buffer, int offset, int count) { ThrowIfDisposed(); - if (m_streamBase is not null) - m_streamBase.Write(buffer, offset, count); + if (WrappedStream is not null) + WrappedStream.Write(buffer, offset, count); } /// - /// Writes a byte to the current position in the stream and advances the position within the stream by one byte. + /// Writes a byte to the current position in the stream and advances the position within the stream by one byte. /// public override void WriteByte(byte value) { ThrowIfDisposed(); - if (m_streamBase is not null) - m_streamBase.WriteByte(value); - } - - /// - /// Gets the wrapped stream. - /// - /// The wrapped stream. - protected Stream? WrappedStream - { - get { return m_streamBase; } + if (WrappedStream is not null) + WrappedStream.WriteByte(value); } /// - /// Releases the unmanaged resources used by the and optionally releases the managed resources. + /// Releases the unmanaged resources used by the and optionally releases the managed + /// resources. /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged + /// resources. + /// protected override void Dispose(bool disposing) { // doesn't close the base stream, but just prevents access to it through this WrappingStream if (disposing) - m_streamBase = null; + WrappedStream = null; base.Dispose(disposing); } @@ -250,8 +246,7 @@ private void ThrowIfDisposed() { // throws an ObjectDisposedException if this object has been disposed - if (m_streamBase == null) + if (WrappedStream == null) throw new ObjectDisposedException(GetType().Name); } - Stream? m_streamBase; } \ No newline at end of file diff --git a/tests/ScreenGrab.Sample/MainWindow.xaml.cs b/tests/ScreenGrab.Sample/MainWindow.xaml.cs index 2c9df59..ee917a9 100644 --- a/tests/ScreenGrab.Sample/MainWindow.xaml.cs +++ b/tests/ScreenGrab.Sample/MainWindow.xaml.cs @@ -1,6 +1,4 @@ using System.Windows; -using Dapplo.Windows.User32; -using ScreenGrab.Extensions; using ScreenGrab.Utilities; namespace ScreenGrab.Sample; @@ -19,58 +17,11 @@ private void Capture_Click(object sender, RoutedEventArgs e) { Clean(); - NewScreenGrab(); - - // var view = new Grab(); - // if (view.ShowDialog() == true) - // { - // var result = view.Image; - // Img.Source = ImageMethods.BitmapToImageSource(result); - // } - } - - private void NewScreenGrab() - { - DisplayInfo[] allScreens = DisplayInfo.AllDisplayInfos; - WindowCollection allWindows = Application.Current.Windows; - - List allFullscreenGrab = new(); - - int numberOfScreens = allScreens.Count(); - - foreach (Window window in allWindows) - if (window is Grab grab) - allFullscreenGrab.Add(grab); - - int numberOfFullscreenGrabWindowsToCreate = numberOfScreens - allFullscreenGrab.Count; - - for (int i = 0; i < numberOfFullscreenGrabWindowsToCreate; i++) + var grab = new Grab { - allFullscreenGrab.Add(new Grab()); - } - - int count = 0; - - double sideLength = 40; - - foreach (DisplayInfo screen in allScreens) - { - Grab fullScreenGrab = allFullscreenGrab[count]; - fullScreenGrab.WindowStartupLocation = WindowStartupLocation.Manual; - fullScreenGrab.Width = sideLength; - fullScreenGrab.Height = sideLength; - fullScreenGrab.WindowState = WindowState.Normal; - - Point screenCenterPoint = screen.ScaledCenterPoint(); - - fullScreenGrab.Left = screenCenterPoint.X - (sideLength / 2); - fullScreenGrab.Top = screenCenterPoint.Y - (sideLength / 2); - - fullScreenGrab.Show(); - fullScreenGrab.Activate(); - - count++; - } + OnImageCaptured = bitmap => Img.Source = bitmap.ToImageSource() + }; + grab.Capture(); } private void Clean_Click(object sender, RoutedEventArgs e)