diff --git a/src/RdpManager/MainWindow.xaml.cs b/src/RdpManager/MainWindow.xaml.cs index b5e293d..bd63fec 100644 --- a/src/RdpManager/MainWindow.xaml.cs +++ b/src/RdpManager/MainWindow.xaml.cs @@ -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(); @@ -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) diff --git a/src/RdpManager/WindowBorder.cs b/src/RdpManager/WindowBorder.cs index b2cdd7f..52ecfd7 100644 --- a/src/RdpManager/WindowBorder.cs +++ b/src/RdpManager/WindowBorder.cs @@ -62,19 +62,30 @@ public static void Keep(Window window) { 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) {