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
13 changes: 11 additions & 2 deletions src/RdpManager/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,7 @@ private void Window_Loaded(object sender, RoutedEventArgs e)
ShowHealthNotices(); // nieblokujący sygnał, jeśli przy ładowaniu zadziałała samonaprawa/kwarantanna

InitTray();
HwndSource.FromHwnd(new WindowInteropHelper(this).Handle)?.AddHook(WndHook);
ApplyHotkey();
ApplyHotkey(); // hook WndProc instalujemy już w OnSourceInitialized (patrz niżej) — przed pierwszą klatką
// Podświetlanie ikon paska kart w trybie skupienia (patrz StartTabStripRepaintPulse): przy ruchu
// myszy nad paskiem wymuszamy przerysowanie, bo WPF sam go w tym trybie nie maluje.
TabStripHost.MouseMove += (_, __) => _fs.StartTabStripRepaintPulse();
Expand Down Expand Up @@ -354,6 +353,16 @@ private void ApplyHotkey()
RegisterHotKey(hwnd, HotkeyId, MOD_CONTROL | MOD_ALT, (uint)'Q');
}

// Hook WndProc + obwódkę zakładamy tu (hwnd już istnieje, okno JESZCZE niepokazane), a nie w Loaded —
// dzięki temu wybrana obwódka („brak"/kolor) obowiązuje od PIERWSZEJ klatki i nie widać błysku akcentu
// (kobalt), którym WPF-UI/DWM maluje krawędź, zanim Loaded zdążyłby ją nadpisać.
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource.FromHwnd(new WindowInteropHelper(this).Handle)?.AddHook(WndHook);
WindowBorder.Apply(this);
}

private IntPtr WndHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_HOTKEY && wParam.ToInt32() == HotkeyId)
Expand Down
29 changes: 20 additions & 9 deletions src/RdpManager/WindowBorder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,30 @@ public static void Keep(Window window)
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Contextual Comment]
This comment refers to code near real line 61. Anchored to nearest_changed(62) line 62.


P2 | Confidence: High

Each invocation of Keep adds a new BorderHook to the window's HwndSource hook chain without removing any previous instance of the same static method. If Keep is called more than once for the same window (e.g., the class handler for Loaded fires multiple times, or window lifecycle is unusual), the BorderHook handler will be registered multiple times. This causes redundant DwmSetWindowAttribute calls on every WM_NCACTIVATE message. While the functional effect is still correct (the same value is set repeatedly), the extra P/Invoke calls are unnecessary and can degrade performance during rapid activation/deactivation sequences. The old code suffered from a similar problem with multiple Activated event subscriptions, so this is not a regression, but migrating to HwndSource hooks provides a clean moment to guard against duplicates.

Code Suggestion:

// Attached property to track whether hook is already installed for this window.
        private static readonly DependencyProperty HookInstalledProperty =
            DependencyProperty.RegisterAttached("HookInstalled", typeof(bool), typeof(WindowBorder),
                new PropertyMetadata(false));

        public static void Keep(Window window)
        {
            if (window == null) return;
            if ((bool)window.GetValue(HookInstalledProperty)) return;
            window.SetValue(HookInstalledProperty, true);
            Apply(window);
            var source = HwndSource.FromHwnd(new WindowInteropHelper(window).Handle);
            if (source == null) return;
            source.AddHook(BorderHook);
            source.Disposed += (_, __) => window.ClearValue(HookInstalledProperty);
            window.Dispatcher.BeginInvoke(new Action(() => Apply(window)),
                System.Windows.Threading.DispatcherPriority.ApplicationIdle);
        }

Evidence: method:Keep, symbol:BorderHook

if (window == null) return;
Apply(window);
// WPF-UI przemalowuje krawędź na akcent PO aktywacji — m.in. gdy zamknie się okno potomne
// (np. „O aplikacji") i główne okno wraca na wierzch. Sam Apply w handlerze aktywacji bywa ZA
// wcześnie (repaint WPF-UI leci później), więc dobijamy jeszcze raz deferred (ApplicationIdle).
window.Activated += (_, __) =>
{
Apply(window);
window.Dispatcher.BeginInvoke(new Action(() => Apply(window)),
System.Windows.Threading.DispatcherPriority.ApplicationIdle);
};
// WM_NCACTIVATE: Windows/WPF-UI przemalowują krawędź (non-client) przy (de)aktywacji — m.in. gdy
// zamknie się okno potomne (np. „O aplikacji") i główne wraca na wierzch. Hookujemy tę wiadomość i
// przywracamy wybraną obwódkę SYNCHRONICZNIE, w tym samym cyklu repaint (bez odroczenia = bez błysku
// kobaltu). Hook jest statyczny (bez domknięcia per-okno) i zwalnia się z HwndSource okna — brak wycieku.
HwndSource.FromHwnd(new WindowInteropHelper(window).Handle)?.AddHook(BorderHook);
// Dobij raz po pełnym wyrenderowaniu (WPF-UI kończy malować krawędź po Loaded) — asekuracja pierwszej klatki.
window.Dispatcher.BeginInvoke(new Action(() => Apply(window)),
System.Windows.Threading.DispatcherPriority.ApplicationIdle);
}

private const int WM_NCACTIVATE = 0x0086;

// Przy każdym WM_NCACTIVATE ponownie nakłada bieżącą specyfikację obwódki — zanim klatka trafi na ekran.
private static IntPtr BorderHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_NCACTIVATE && hwnd != IntPtr.Zero)
{
uint val = SpecToColorRef(_spec);
try { DwmSetWindowAttribute(hwnd, DWMWA_BORDER_COLOR, ref val, sizeof(uint)); }
catch { /* starszy DWM bez atrybutu — bez znaczenia */ }
}
return IntPtr.Zero;
}

// "" → brak; "System"/"default" → systemowy akcent; "#RRGGBB" → COLORREF (0x00BBGGRR); błędny → brak.
private static uint SpecToColorRef(string spec)
{
Expand Down
Loading