Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 150 additions & 6 deletions Source/NETworkManager.Utilities/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public class NativeMethods
public const long WS_POPUP = 0x80000000L;
public const long WS_CAPTION = 0x00C00000L;

/// <summary>The value returned by CreateFile on failure.</summary>
public static readonly IntPtr INVALID_HANDLE_VALUE = new(-1);

#endregion

#region Enum
Expand Down Expand Up @@ -44,11 +47,37 @@ public enum WM : uint

#endregion

#region Pinvoke/Win32 Methods
#region Structs

[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left, top, right, bottom;
}

[StructLayout(LayoutKind.Sequential)]
public struct COORD
{
public short X;
public short Y;
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CONSOLE_FONT_INFOEX
{
public uint cbSize;
public uint nFont;
public COORD dwFontSize;
public uint FontFamily;
public uint FontWeight;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string FaceName;
}

#endregion

#region Pinvoke/Win32 Methods

[DllImport("user32.dll", SetLastError = true)]
public static extern long SetParent(IntPtr hWndChild, IntPtr hWndParent);

Expand Down Expand Up @@ -76,14 +105,129 @@ public static extern IntPtr SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, in
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll", SetLastError = true)]
public static extern bool MoveWindow(IntPtr hWnd, int x, int y, int cx, int cy, bool repaint);

[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, WindowShowStyle nCmdShow);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetForegroundWindow(IntPtr hWnd);

/// <summary>
/// Returns the DPI (dots per inch) value for the monitor that contains the specified window.
/// Returns 0 if the window handle is invalid. Available on Windows 10 version 1607+.
/// </summary>
[DllImport("user32.dll")]
public static extern uint GetDpiForWindow(IntPtr hWnd);

[DllImport("user32.dll", SetLastError = true)]
public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool AttachConsole(uint dwProcessId);

[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool FreeConsole();

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode,
IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile);

[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool GetCurrentConsoleFontEx(IntPtr hConsoleOutput, bool bMaximumWindow,
ref CONSOLE_FONT_INFOEX lpConsoleCurrentFontEx);

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool SetCurrentConsoleFontEx(IntPtr hConsoleOutput, bool bMaximumWindow,
ref CONSOLE_FONT_INFOEX lpConsoleCurrentFontEx);

#endregion

#region Helpers

/// <summary>
/// Attaches to <paramref name="processId"/>'s console and rescales its current font
/// by <paramref name="scaleFactor"/> using <c>SetCurrentConsoleFontEx</c>.
/// This is a cross-process-safe approach that bypasses WM_DPICHANGED message passing
/// entirely. Works for any conhost-based console (PowerShell, cmd, etc.).
/// </summary>
public static void TryRescaleConsoleFont(uint processId, double scaleFactor)
{
if (Math.Abs(scaleFactor - 1.0) < 0.01)
return;

if (!AttachConsole(processId))
return;

const uint GENERIC_READ_WRITE = 0xC0000000u;
const uint FILE_SHARE_READ_WRITE = 3u;
const uint OPEN_EXISTING = 3u;

var hOut = CreateFile("CONOUT$", GENERIC_READ_WRITE, FILE_SHARE_READ_WRITE,
IntPtr.Zero, OPEN_EXISTING, 0u, IntPtr.Zero);

try
{
if (hOut == INVALID_HANDLE_VALUE)
return;

try
{
var fi = new CONSOLE_FONT_INFOEX { cbSize = (uint)Marshal.SizeOf<CONSOLE_FONT_INFOEX>() };
if (GetCurrentConsoleFontEx(hOut, false, ref fi))
{
fi.dwFontSize.Y = (short)Math.Max(1, (int)Math.Round(fi.dwFontSize.Y * scaleFactor));
fi.cbSize = (uint)Marshal.SizeOf<CONSOLE_FONT_INFOEX>();
SetCurrentConsoleFontEx(hOut, false, ref fi);
}
}
finally
{
CloseHandle(hOut);
}
}
finally
{
FreeConsole();
}
}

/// <summary>
/// Sends a <c>WM_DPICHANGED</c> message to a GUI window (e.g. PuTTY) so it can
/// rescale its fonts and layout internally. This is necessary because
/// <c>WM_DPICHANGED</c> is not reliably forwarded to cross-process child windows
/// embedded via <c>SetParent</c>. Requires PuTTY 0.75+ to take effect.
/// </summary>
public static void TrySendDpiChangedMessage(IntPtr hWnd, double oldDpi, double newDpi)
{
if (hWnd == IntPtr.Zero)
return;

if (Math.Abs(newDpi - oldDpi) < 0.01)
return;

const uint WM_DPICHANGED = 0x02E0;

var newDpiInt = (int)Math.Round(newDpi);
var wParam = (IntPtr)((newDpiInt << 16) | newDpiInt); // HIWORD = Y DPI, LOWORD = X DPI

// Build the suggested new rect from the current window position.
var rect = new RECT();
GetWindowRect(hWnd, ref rect);

// lParam must point to a RECT with the suggested new size/position.
var lParam = Marshal.AllocHGlobal(Marshal.SizeOf<RECT>());
try
{
Marshal.StructureToPtr(rect, lParam, false);
SendMessage(hWnd, WM_DPICHANGED, wParam, lParam);
}
finally
{
Marshal.FreeHGlobal(lParam);
}
}

#endregion
}
3 changes: 2 additions & 1 deletion Source/NETworkManager/Controls/PowerShellControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
</local:UserControlBase.Resources>
<Grid SizeChanged="WindowGrid_SizeChanged">
<!-- Background color will prevent flickering when app inside the panel is closed -->
<WindowsFormsHost Background="{DynamicResource MahApps.Brushes.Window.Background}" Margin="10">
<WindowsFormsHost Background="{DynamicResource MahApps.Brushes.Window.Background}" Margin="10"
DpiChanged="WindowsFormsHost_DpiChanged">
<WindowsFormsHost.Style>
<Style TargetType="{x:Type WindowsFormsHost}">
<Style.Triggers>
Expand Down
32 changes: 28 additions & 4 deletions Source/NETworkManager/Controls/PowerShellControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,19 @@ private void WindowGrid_SizeChanged(object sender, SizeChangedEventArgs e)
ResizeEmbeddedWindow();
}

private void WindowsFormsHost_DpiChanged(object sender, DpiChangedEventArgs e)
{
ResizeEmbeddedWindow();

if (!IsConnected)
return;

// Rescale the console font of the embedded conhost process so it remains the same physical size when the DPI changes.
NativeMethods.TryRescaleConsoleFont(
(uint)_process.Id,
e.NewDpi.PixelsPerInchX / e.OldDpi.PixelsPerInchX);
}

#endregion

#region Variables
Expand Down Expand Up @@ -89,8 +102,10 @@ private void UserControl_Loaded(object sender, RoutedEventArgs e)

// Fix 1: The control is not visible by default, thus height and width is not set. If the values are not set, the size does not scale properly
// Fix 2: Somehow the initial size need to be 20px smaller than the actual size after using Dragablz (https://github.com/BornToBeRoot/NETworkManager/pull/2678)
WindowHost.Height = (int)ActualHeight - 20;
WindowHost.Width = (int)ActualWidth - 20;
// Fix 3: The size needs to be scaled by the DPI, otherwise the embedded window is too small on high DPI screens (https://github.com/BornToBeRoot/NETworkManager/pull/3352)
var dpi = System.Windows.Media.VisualTreeHelper.GetDpi(this);
WindowHost.Height = (int)((ActualHeight - 20) * dpi.DpiScaleY);
WindowHost.Width = (int)((ActualWidth - 20) * dpi.DpiScaleX);

Connect().ConfigureAwait(false);

Expand Down Expand Up @@ -165,6 +180,9 @@ private async Task Connect()

if (_appWin != IntPtr.Zero)
{
// Capture DPI before embedding to correct font scaling afterwards
var initialWindowDpi = NativeMethods.GetDpiForWindow(_appWin);

NativeMethods.SetParent(_appWin, WindowHost.Handle);

// Show window before set style and resize
Expand All @@ -177,10 +195,16 @@ private async Task Connect()

IsConnected = true;

// Resize embedded application & refresh
// Requires a short delay because it's not applied immediately
// Resize after short delay — not applied immediately
await Task.Delay(250);

ResizeEmbeddedWindow();

// Correct font if conhost started at a different DPI than our panel
var currentPanelDpi = NativeMethods.GetDpiForWindow(WindowHost.Handle);

if (initialWindowDpi != currentPanelDpi)
NativeMethods.TryRescaleConsoleFont((uint)_process.Id, (double)currentPanelDpi / initialWindowDpi);
}
}
else
Expand Down
3 changes: 2 additions & 1 deletion Source/NETworkManager/Controls/PuTTYControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
</local:UserControlBase.Resources>
<Grid SizeChanged="WindowGrid_SizeChanged">
<!-- Background color will prevent flickering when app inside the panel is closed -->
<WindowsFormsHost Background="{DynamicResource MahApps.Brushes.Window.Background}" Margin="10">
<WindowsFormsHost Background="{DynamicResource MahApps.Brushes.Window.Background}" Margin="10"
DpiChanged="WindowsFormsHost_DpiChanged">
<WindowsFormsHost.Style>
<Style TargetType="{x:Type WindowsFormsHost}">
<Style.Triggers>
Expand Down
37 changes: 29 additions & 8 deletions Source/NETworkManager/Controls/PuTTYControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ private void WindowGrid_SizeChanged(object sender, SizeChangedEventArgs e)
ResizeEmbeddedWindow();
}

private void WindowsFormsHost_DpiChanged(object sender, DpiChangedEventArgs e)
{
ResizeEmbeddedWindow();

if (!IsConnected)
return;

// Send WM_DPICHANGED to the embedded window so it can rescale fonts and UI elements.
NativeMethods.TrySendDpiChangedMessage(
_appWin,
e.OldDpi.PixelsPerInchX,
e.NewDpi.PixelsPerInchX);
}
Comment on lines +25 to +37
Copy link

Copilot AI Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description details a specific PuTTY DPI fix that involves: (1) extracting a TriggerDpiUpdateAsync() method, (2) temporarily restoring WS_POPUP before calling SetParent(null) so Windows recognises the window as a top-level popup and delivers WM_DPICHANGED, and (3) sizing the detached window to 800×600 to exceed Windows's monitor-DPI detection threshold. None of these are actually implemented.

The PR description explicitly states that without WS_POPUP the window has no recognised top-level style and "Windows never queues WM_DPICHANGED for it." Yet the actual implementation in WindowsFormsHost_DpiChanged simply calls TrySendDpiChangedMessage on the already-embedded child window (which no longer has WS_POPUP), without any detach/reattach cycle. This contradicts the stated root cause.

If the described root cause is correct, the current implementation—sending WM_DPICHANGED directly while the window is a WS_CHILD embedded window without WS_POPUP—may not be effective for PuTTY DPI rescaling.

Copilot uses AI. Check for mistakes.
#endregion

#region Variables
Expand Down Expand Up @@ -90,8 +103,10 @@ private void UserControl_Loaded(object sender, RoutedEventArgs e)

// Fix 1: The control is not visible by default, thus height and width is not set. If the values are not set, the size does not scale properly
// Fix 2: Somehow the initial size need to be 20px smaller than the actual size after using Dragablz (https://github.com/BornToBeRoot/NETworkManager/pull/2678)
WindowHost.Height = (int)ActualHeight - 20;
WindowHost.Width = (int)ActualWidth - 20;
// Fix 3: The size needs to be scaled by the DPI, otherwise the embedded window is too small on high DPI screens (https://github.com/BornToBeRoot/NETworkManager/pull/3352)
var dpi = System.Windows.Media.VisualTreeHelper.GetDpi(this);
WindowHost.Height = (int)((ActualHeight - 20) * dpi.DpiScaleY);
WindowHost.Width = (int)((ActualWidth - 20) * dpi.DpiScaleX);

Connect().ConfigureAwait(false);

Expand Down Expand Up @@ -178,25 +193,31 @@ private async Task Connect()

if (!_process.HasExited)
{
// Capture DPI before embedding to correct font scaling afterwards
var initialWindowDpi = NativeMethods.GetDpiForWindow(_appWin);

NativeMethods.SetParent(_appWin, WindowHost.Handle);

// Show window before set style and resize
NativeMethods.ShowWindow(_appWin, NativeMethods.WindowShowStyle.Maximize);

// Remove border etc.
// Remove border etc.
long style = (int)NativeMethods.GetWindowLong(_appWin, NativeMethods.GWL_STYLE);
style &= ~(NativeMethods.WS_CAPTION | NativeMethods.WS_POPUP |
NativeMethods
.WS_THICKFRAME); // NativeMethods.WS_POPUP --> Overflow? (https://github.com/BornToBeRoot/NETworkManager/issues/167)
style &= ~(NativeMethods.WS_CAPTION | NativeMethods.WS_POPUP | NativeMethods.WS_THICKFRAME);
NativeMethods.SetWindowLongPtr(_appWin, NativeMethods.GWL_STYLE, new IntPtr(style));

IsConnected = true;

// Resize embedded application & refresh
// Requires a short delay because it's not applied immediately
// Resize after short delay — not applied immediately
await Task.Delay(250);

ResizeEmbeddedWindow();

// Correct DPI if PuTTY started at a different DPI than our panel
var currentPanelDpi = NativeMethods.GetDpiForWindow(WindowHost.Handle);

if (initialWindowDpi != currentPanelDpi)
NativeMethods.TrySendDpiChangedMessage(_appWin, initialWindowDpi, currentPanelDpi);
}
}
}
Expand Down
6 changes: 4 additions & 2 deletions Source/NETworkManager/Controls/TigerVNCControl.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,10 @@ private void UserControl_Loaded(object sender, RoutedEventArgs e)
return;

// Fix 1: The control is not visible by default, thus height and width is not set. If the values are not set, the size does not scale properly
WindowHost.Height = (int)ActualHeight;
WindowHost.Width = (int)ActualWidth;
// Fix 2: The size needs to be scaled by the DPI, otherwise the embedded window is too small on high DPI screens (https://github.com/BornToBeRoot/NETworkManager/pull/3352)
var dpi = System.Windows.Media.VisualTreeHelper.GetDpi(this);
WindowHost.Height = (int)(ActualHeight * dpi.DpiScaleY);
WindowHost.Width = (int)(ActualWidth * dpi.DpiScaleX);

Connect().ConfigureAwait(false);

Expand Down
1 change: 1 addition & 0 deletions Website/blog/2024-01-01-welcome/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
slug: welcome
title: Welcome
description: "Welcome to the new NETworkManager website, blod and documentation. It's build with Docusaurus, a static website generator, and hosted on GitHub Pages."
authors: [borntoberoot]
tags: [welcome, docusaurus]
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
slug: introducing-code-signing-for-binaries
title: Introducing Code Signing for Binaries
description: "Starting with NETworkManager version 2024.5.24.0, the binaries and the installer are now signed with a code signing certificate."
authors: [borntoberoot]
tags: [code-signing, binaries, installer]
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
slug: introducing-hosts-file-editor
title: Introducing Hosts File Editor
description: "NETworkManager 2025.8.10.0 introduced a new feature, the `Hosts File Editor`. You can now easily manage and edit your system's hosts file in a user-friendly interface."
authors: [borntoberoot]
tags: [hosts file, dns, new feature]
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
slug: streamlined-profile-management-with-tags-and-filters
title: Streamlined Profile Management with Tags & Filters
description: "NETworkManager 2025.10.18.0 brings a streamlined approach to profile management with the introduction of Tags and Filtering. You can now add or remove tags directly within each profile, making it effortless to organize and quickly locate your hosts and networks."
authors: [borntoberoot]
tags: [profile management, tags, new feature]
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
slug: improved-light-theme-readability
title: Improved light theme readability
description: "The latest release of NETworkManager (version 2025.10.18.0) introduces major enhancements to the light theme, improving both readability and overall user experience. This article highlights the changes made to the light theme and how they enhance usability."
authors: [borntoberoot]
tags: [theme, ui]
---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
slug: system-wide-policies-for-enterprise-deployments
title: System-Wide Policies for Enterprise Deployments
description: "NETworkManager now supports system-wide policies, giving administrators centralized control over application settings across all users on a machine. This article explains how to configure and deploy these policies in enterprise environments."
authors: [borntoberoot]
tags: [policies, enterprise, settings, new feature]
---
Expand Down
Loading