using System.Runtime.InteropServices; using Avalonia.Controls; using Avalonia.Media; namespace DodoSSH.Client.App.Views; /// /// Paints the frame Windows still draws around a BorderOnly window in the application's own /// colour, so the top edge stops reading as a leftover system titlebar. /// /// /// /// The symptom this exists for: a pale strip across the very top of the window, a few pixels /// tall and plainly not part of the application — most obvious on a machine with "show accent colour /// on title bars and window borders" turned on, where it comes out blue against a near-black shell. /// /// /// It is not TitleBar.axaml leaking and it is not a margin. It is DWM, and the reason it is /// there is visible in Avalonia's own Win32 backend: WindowImpl.UpdateWindowProperties gives a /// window WS_BORDER | WS_THICKFRAME and then calls /// DwmExtendFrameIntoClientArea with one-pixel margins on all four sides. So the compositor /// owns a hairline of every edge of this window, and it fills that hairline with the system's caption /// and border colours — which are chosen by the user's personalisation settings and have no reason to /// resemble CanvasColor. The window is the wrong place to look for the pixels; they were never /// painted by anything in this tree. /// /// /// The fix is to tell DWM what colour to use rather than to try to cover it. DWMWA_BORDER_COLOR /// and DWMWA_CAPTION_COLOR arrived in Windows 11 21H2 and are exactly that; both are set to the /// window's own background, so the hairline still exists — the resize grip is on it, and the drop /// shadow hangs off it — and simply cannot be seen. Deliberately not DWMWA_COLOR_NONE, /// which removes the border outright: on a dark desktop that leaves a near-black window with no edge /// at all, which trades one visual defect for another. /// /// /// Windows 10 gets the dark-mode attribute and nothing else, and that is the whole of what is /// available there: the two colour attributes are unsupported, DwmSetWindowAttribute answers /// E_INVALIDARG, and the calls do nothing. Hence the ignored return values — every attribute /// here is an improvement where it lands and a no-op where it does not, so there is nothing for a /// caller to handle and nothing worth logging on a path that runs once at startup. /// /// internal static class NativeWindowFrame { /// Windows 11 21H2 and later: the colour of the frame border. private const int BorderColorAttribute = 34; /// Windows 11 21H2 and later: the colour of the caption, including the extended frame. private const int CaptionColorAttribute = 35; /// /// Windows 10 1903 and later: draw the frame in the dark palette. /// /// /// Redundant on Windows 11, where the two colour attributes above name the colours outright, and it /// is set anyway because it is the only one of the three that Windows 10 honours. The build before /// 1903 used attribute 19 for this; that is not chased here, because a border on an OS release that /// left support in 2020 is not worth a second interop call. /// private const int DarkModeAttribute = 20; /// /// Matches 's system-drawn frame to the colour it paints itself. /// /// /// Call once the window has a handle — OnOpened is the first such moment. Calling earlier /// finds no platform handle and silently does nothing, which is the defect this replaced: the strip /// is only visible once the window is on screen, so a call that ran too early looks like a fix that /// does not work rather than a fix that never ran. /// internal static void MatchTo(Window window) { // Every attribute below is a DWM one, and DWM is Windows. Elsewhere the frame is drawn by the // platform's own compositor and there is nothing here to say to it. if (!OperatingSystem.IsWindows()) { return; } if (window.TryGetPlatformHandle()?.Handle is not { } handle || handle == IntPtr.Zero) { return; } Set(handle, DarkModeAttribute, 1); // The window's own Background rather than a named resource, so the frame cannot drift from the // canvas when the palette moves. A brush that is not solid — a gradient, or nothing set at all — // has no single colour to match, and leaving the system's own is better than inventing one. if (window.Background is not ISolidColorBrush { Color: var canvas }) { return; } var reference = ColorRef(canvas); Set(handle, BorderColorAttribute, reference); Set(handle, CaptionColorAttribute, reference); } /// /// Sets one integer-valued DWM attribute, and discards the answer. /// /// /// The discard is the point of this method existing rather than being three call sites. Every /// attribute here is unsupported on some Windows this application runs on, and unsupported means /// E_INVALIDARG and no change — which is the intended outcome on that OS, not a failure, so /// there is nothing for the caller to do with the HRESULT and nothing worth logging once at /// startup. Written once, with the reasoning, rather than left implicit at each call. /// private static void Set(IntPtr window, int attribute, int value) => _ = DwmSetWindowAttribute(window, attribute, ref value, sizeof(int)); /// /// Packs into a Win32 COLORREF. /// /// /// 0x00BBGGRR — blue in the high byte, not red, and the alpha byte must be zero. Getting the /// order wrong produces a plausible-looking wrong colour rather than an error, which is the kind of /// bug that survives a glance at the window. /// private static int ColorRef(Color color) => color.R | (color.G << 8) | (color.B << 16); /// /// DllImport rather than LibraryImport, for the reason /// gives at its own P/Invoke: the generated form needs /// AllowUnsafeBlocks across a project that handles key material, and this signature is /// blittable, so there is no marshalling for it to improve. /// #pragma warning disable SYSLIB1054 [DllImport("dwmapi.dll")] private static extern int DwmSetWindowAttribute(IntPtr window, int attribute, ref int value, int size); #pragma warning restore SYSLIB1054 }