diff --git a/AGENTS.md b/AGENTS.md index 8cd0de9c93..342e79f0e2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -92,7 +92,8 @@ REPOSITORY_ROOT/ - **DnsClient** - DNS lookups - **IPNetwork2** - Network and subnet calculations - **PSDiscoveryProtocol** - LLDP/CDP network discovery (PowerShell module embedded) -- **AirspaceFixer** - WPF/WinForms interop fixes +- **AirspaceFixer** - WPF/WinForms interop fixes (code embedded as `AirspacePanel` in `NETworkManager.Controls`, + based on AirspaceFixer and its fork AirspaceFixerDotNet) - **log4net** - Logging framework - **LiveChartsCore.SkiaSharpView.WPF** - Charts and graphs - **Microsoft.Web.WebView2** - WebView2 control for web content diff --git a/Source/NETworkManager.Controls/AirspacePanel.cs b/Source/NETworkManager.Controls/AirspacePanel.cs new file mode 100644 index 0000000000..e03927cc18 --- /dev/null +++ b/Source/NETworkManager.Controls/AirspacePanel.cs @@ -0,0 +1,159 @@ +// Based on AirspaceFixer (https://github.com/chris84948/AirspaceFixer) and its fork AirspaceFixerDotNet +// (https://github.com/ellipticbit/AirspaceFixerDotNet), adapted for NETworkManager. +// +// MIT License +// +// Copyright (c) 2017 chris84948 +// Copyright (c) 2024 EllipticBit, LLC +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Drawing.Imaging; +using System.IO; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media.Imaging; + +namespace NETworkManager.Controls; + +/// +/// Hosts content with embedded windows (e.g. Remote Desktop, PuTTY, WebView2), which are always drawn on top of +/// WPF elements (airspace issue). While is , the content is +/// replaced by a screenshot of it, so that WPF elements like dialogs can be shown above it. +/// The template is defined in Resources/Styles/AirspacePanelStyles.xaml of the application. +/// +[TemplatePart(Name = PartAirspaceContent, Type = typeof(ContentControl))] +[TemplatePart(Name = PartAirspaceScreenshot, Type = typeof(Image))] +public class AirspacePanel : ContentControl +{ + private const string PartAirspaceContent = "PART_AirspaceContent"; + private const string PartAirspaceScreenshot = "PART_AirspaceScreenshot"; + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty FixAirspaceProperty = DependencyProperty.Register(nameof(FixAirspace), + typeof(bool), typeof(AirspacePanel), new FrameworkPropertyMetadata(false, OnFixAirspaceChanged)); + + private ContentControl _airspaceContent; + private Image _airspaceScreenshot; + + /// + /// Replace the content with a screenshot of it (e.g. while a dialog is shown above the content). + /// + public bool FixAirspace + { + get => (bool)GetValue(FixAirspaceProperty); + set => SetValue(FixAirspaceProperty, value); + } + + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _airspaceContent = GetTemplateChild(PartAirspaceContent) as ContentControl; + _airspaceScreenshot = GetTemplateChild(PartAirspaceScreenshot) as Image; + } + + private static void OnFixAirspaceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is AirspacePanel panel) + panel.UpdateAirspace((bool)e.NewValue); + } + + private void UpdateAirspace(bool fix) + { + // Template not applied (yet) + if (_airspaceContent == null || _airspaceScreenshot == null) + return; + + if (fix) + { + // Content not visible or not shown in a window - nothing to capture, keep the content + var screenshot = CreateScreenshotFromContent(); + + if (screenshot == null) + return; + + // Show the screenshot in the size of the content when it was taken (top left aligned in the template), so + // it is not stretched / scaled if the window is resized while it is shown + _airspaceScreenshot.Width = _airspaceContent.ActualWidth; + _airspaceScreenshot.Height = _airspaceContent.ActualHeight; + _airspaceScreenshot.Source = screenshot; + _airspaceContent.Visibility = Visibility.Hidden; + _airspaceScreenshot.Visibility = Visibility.Visible; + } + else + { + // Always show the content again, even if the size changed in the meantime + _airspaceContent.Visibility = Visibility.Visible; + _airspaceScreenshot.Visibility = Visibility.Hidden; + _airspaceScreenshot.Source = null; + _airspaceScreenshot.ClearValue(WidthProperty); + _airspaceScreenshot.ClearValue(HeightProperty); + } + } + + private BitmapSource CreateScreenshotFromContent() + { + var presentationSource = PresentationSource.FromVisual(_airspaceContent); + + if (presentationSource?.CompositionTarget == null) + return null; + + // The application is per monitor DPI aware (PerMonitorV2, see app.manifest): PointToScreen returns physical + // pixels, but the size is in device independent pixels and has to be scaled with the DPI of the monitor. + var transformToDevice = presentationSource.CompositionTarget.TransformToDevice; + + var upperLeftPoint = _airspaceContent.PointToScreen(new Point(0, 0)); + var width = (int)Math.Round(_airspaceContent.ActualWidth * transformToDevice.M11); + var height = (int)Math.Round(_airspaceContent.ActualHeight * transformToDevice.M22); + + if (width <= 0 || height <= 0) + return null; + + using var bitmap = new System.Drawing.Bitmap(width, height); + + using (var graphics = System.Drawing.Graphics.FromImage(bitmap)) + { + graphics.CopyFromScreen((int)Math.Round(upperLeftPoint.X), (int)Math.Round(upperLeftPoint.Y), 0, 0, + new System.Drawing.Size(width, height)); + } + + return GetBitmapSource(bitmap); + } + + private static BitmapSource GetBitmapSource(System.Drawing.Bitmap bitmap) + { + using var memoryStream = new MemoryStream(); + + bitmap.Save(memoryStream, ImageFormat.Png); + memoryStream.Position = 0; + + var bitmapImage = new BitmapImage(); + bitmapImage.BeginInit(); + bitmapImage.StreamSource = memoryStream; + bitmapImage.CacheOption = BitmapCacheOption.OnLoad; + bitmapImage.EndInit(); + bitmapImage.Freeze(); + + return bitmapImage; + } +} diff --git a/Source/NETworkManager.Documentation/LibraryManager.cs b/Source/NETworkManager.Documentation/LibraryManager.cs index 5844e094ac..91e034774a 100644 --- a/Source/NETworkManager.Documentation/LibraryManager.cs +++ b/Source/NETworkManager.Documentation/LibraryManager.cs @@ -28,6 +28,10 @@ public static class LibraryManager Strings.Library_AirspaceFixer_Description, Strings.License_MITLicense, "https://github.com/chris84948/AirspaceFixer/blob/master/LICENSE"), + new("AirspaceFixerDotNet", "https://github.com/ellipticbit/AirspaceFixerDotNet", + Strings.Library_AirspaceFixer_Description, + Strings.License_MITLicense, + "https://github.com/ellipticbit/AirspaceFixerDotNet/blob/master/LICENSE"), new("ControlzEx", "https://github.com/ControlzEx/ControlzEx", Strings.Library_ControlzEx_Description, Strings.License_MITLicense, diff --git a/Source/NETworkManager.Documentation/Licenses/AirspaceFixerDotNet.txt b/Source/NETworkManager.Documentation/Licenses/AirspaceFixerDotNet.txt new file mode 100644 index 0000000000..105eb6eb9b --- /dev/null +++ b/Source/NETworkManager.Documentation/Licenses/AirspaceFixerDotNet.txt @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2017 chris84948 +Copyright (c) 2024 EllipticBit, LLC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/Source/NETworkManager.Documentation/NETworkManager.Documentation.csproj b/Source/NETworkManager.Documentation/NETworkManager.Documentation.csproj index ce00c84acb..4b1ceece5c 100644 --- a/Source/NETworkManager.Documentation/NETworkManager.Documentation.csproj +++ b/Source/NETworkManager.Documentation/NETworkManager.Documentation.csproj @@ -17,6 +17,7 @@ + @@ -48,6 +49,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/Source/NETworkManager/App.xaml b/Source/NETworkManager/App.xaml index 36c4e5454a..730d0e4469 100644 --- a/Source/NETworkManager/App.xaml +++ b/Source/NETworkManager/App.xaml @@ -24,6 +24,7 @@ + diff --git a/Source/NETworkManager/MainWindow.xaml b/Source/NETworkManager/MainWindow.xaml index 55261c4355..c844512895 100644 --- a/Source/NETworkManager/MainWindow.xaml +++ b/Source/NETworkManager/MainWindow.xaml @@ -10,7 +10,7 @@ xmlns:shell="clr-namespace:ControlzEx.Windows.Shell;assembly=ControlzEx" xmlns:controls="clr-namespace:NETworkManager.Controls" xmlns:interactivity="http://schemas.microsoft.com/xaml/behaviors" - xmlns:asf="clr-namespace:AirspaceFixer;assembly=AirspaceFixer" + xmlns:networkManagerControls="clr-namespace:NETworkManager.Controls;assembly=NETworkManager.Controls" xmlns:wpfHelpers="clr-namespace:NETworkManager.Utilities.WPF;assembly=NETworkManager.Utilities.WPF" xmlns:localization="clr-namespace:NETworkManager.Localization.Resources;assembly=NETworkManager.Localization" xmlns:settings="clr-namespace:NETworkManager.Settings;assembly=NETworkManager.Settings" @@ -973,10 +973,10 @@ - + - + diff --git a/Source/NETworkManager/NETworkManager.csproj b/Source/NETworkManager/NETworkManager.csproj index b711ec7b83..f7162f8073 100644 --- a/Source/NETworkManager/NETworkManager.csproj +++ b/Source/NETworkManager/NETworkManager.csproj @@ -61,7 +61,6 @@ - diff --git a/Source/NETworkManager/Resources/Styles/AirspacePanelStyles.xaml b/Source/NETworkManager/Resources/Styles/AirspacePanelStyles.xaml new file mode 100644 index 0000000000..7ff45d06fd --- /dev/null +++ b/Source/NETworkManager/Resources/Styles/AirspacePanelStyles.xaml @@ -0,0 +1,29 @@ + + + + diff --git a/Website/docs/changelog/next-release.md b/Website/docs/changelog/next-release.md index 60f2403fc7..82a2f50fff 100644 --- a/Website/docs/changelog/next-release.md +++ b/Website/docs/changelog/next-release.md @@ -189,6 +189,7 @@ Release date: **xx.xx.2026** - Fixed the application list not scrolling to the selected application if it was opened from somewhere else (e.g. from the **Dashboard**, another application or the run command). [#3644](https://github.com/BornToBeRoot/NETworkManager/pull/3644) - Fixed `Enter` in the application search (title bar) being able to trigger the default button of the shown tool (e.g. start a scan in the **IP Scanner**). It only filters the applications now. [#3660](https://github.com/BornToBeRoot/NETworkManager/pull/3660) - Fixed columns that couldn't be made wider than the window (by dragging or double-clicking the gripper) in **About** and the DNS server profiles in **Settings > Network**. The grid scrolls horizontally now, the last (or main) column still fills the remaining width. [#3557](https://github.com/BornToBeRoot/NETworkManager/issues/3557) +- Fixed the screenshot that is shown in place of embedded windows (**Remote Desktop**, **PowerShell**, **PuTTY**, **TigerVNC**, **Web Console**) while a dialog is open being cropped / stretched with a display scaling above 100% or on a monitor with a different scaling, and being stretched / scaled if the window is resized while the dialog is open (it keeps its size now). The `AirspacePanel` of [AirspaceFixer](https://github.com/chris84948/AirspaceFixer) is now part of NETworkManager, based on the fork [AirspaceFixerDotNet](https://github.com/ellipticbit/AirspaceFixerDotNet) with per monitor DPI support, which also fixes a GDI handle leak. [#3681](https://github.com/BornToBeRoot/NETworkManager/pull/3681) **Firewall**