Skip to content

fix(ui): obwódka okna nie błyska na kobalt przy starcie i aktywacji#183

Merged
FilipB97 merged 1 commit into
masterfrom
claude/border-flash-fix
Jul 10, 2026
Merged

fix(ui): obwódka okna nie błyska na kobalt przy starcie i aktywacji#183
FilipB97 merged 1 commit into
masterfrom
claude/border-flash-fix

Conversation

@FilipB97

Copy link
Copy Markdown
Owner

Problem

Obwódka okna błyska na kobalt (#2657D6) przy uruchomieniu i przy niektórych akcjach, mimo ustawienia obwódki na „brak"/„systemowa".

Przyczyna

Krawędź na Win11 to DWMWA_BORDER_COLOR. WPF-UI/DWM maluje ją akcentem (kobalt) przy pierwszym pokazaniu okna, przy każdej (de)aktywacji i po zmianie motywu. Wybraną obwódkę („brak" = DWMWA_COLOR_NONE) nakładaliśmy reaktywnie i z odroczeniem:

  • WindowBorder.SetSpec na starcie leci, zanim istnieje jakiekolwiek okno (App.xaml.cs).
  • Per-okno NONE dopiero na Loaded (class-handler → WindowBorder.Keep) + odroczony ApplicationIdle — czyli po pierwszym malowaniu.
  • Hook WM_NCACTIVATE w MainWindow był instalowany dopiero w Loaded.

Efekt: okno/dialog na moment pokazuje kobaltową krawędź, którą gasimy ułamek sekundy później.

Poprawka

  • MainWindow: hook WndProc + WindowBorder.Apply(this) przeniesione z Loaded do OnSourceInitialized (hwnd już jest, okno jeszcze niepokazane) → wybrana obwódka obowiązuje od pierwszej klatki.
  • WindowBorder.Keep: zamiast odroczonego ApplicationIdle instaluje hook WM_NCACTIVATE na każdym oknie — korekta krawędzi jest synchroniczna, w tym samym cyklu repaint (obejmuje też dialogi przy otwieraniu/zamykaniu).

Weryfikacja

⚠️ Zmiana zależna od DWM/Win11, a WPF nie uruchomię tutaj (Linux) — kompilację potwierdza CI na Windows, ale wizualne zniknięcie błysku trzeba sprawdzić u Ciebie na Win11 (start aplikacji, otwarcie/zamknięcie okna „O aplikacji"/edytora, zmiana motywu).

Jeśli na dialogach nadal mignie pierwsza klatka, dołożę nałożenie obwódki w ich OnSourceInitialized (wymaga wspólnego punktu bazowego) — celowo zacząłem od pewnych, niskiego ryzyka zmian.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KXXgwUeSkZsXYVKRzyMvV9


Generated by Claude Code

Przyczyna: wybraną obwódkę (DWMWA_BORDER_COLOR — „brak"/kolor) nakładaliśmy reaktywnie i z
odroczeniem (Loaded + ApplicationIdle), a WPF-UI/DWM maluje krawędź akcentem (kobalt) wcześniej —
przy pierwszym pokazaniu okna, przy każdej (de)aktywacji i po zmianie motywu — stąd błysk.

- MainWindow: hook WndProc + WindowBorder.Apply przeniesione z Loaded do OnSourceInitialized
  (hwnd już istnieje, okno jeszcze niepokazane) → wybrana obwódka obowiązuje od pierwszej klatki.
- WindowBorder.Keep: zamiast odroczonego ApplicationIdle instaluje hook WM_NCACTIVATE na każdym
  oknie — korekta krawędzi jest synchroniczna, w tym samym cyklu repaint (dotyczy też dialogów).

Zmiana wizualna (DWM/Win11) — do weryfikacji na Windows 11.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KXXgwUeSkZsXYVKRzyMvV9
@FilipB97
FilipB97 marked this pull request as ready for review July 10, 2026 21:11
@FilipB97
FilipB97 merged commit 3efd669 into master Jul 10, 2026
8 checks passed

@llamapreview llamapreview Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI Code Review by LlamaPReview

🎯 TL;DR & Recommendation

Recommendation: Approve with suggestions

This PR eliminates the cobalt border flash at startup and activation by moving border application from Loaded/ApplicationIdle to OnSourceInitialized and using a WM_NCACTIVATE hook for synchronous reapplies. The logic is sound and the risk is low, though there is a minor maintainability concern with duplicate hook registration.

📄 Documentation Diagram

This diagram documents the refactored window border application flow, ensuring the border is set before the first frame and reapplied synchronously on activation.

sequenceDiagram
participant Window as MainWindow
participant WB as WindowBorder
participant DWM as DWM (Windows)

Note over Window,WB: OnSourceInitialized (early)
Window->>WB: Apply(this)
WB->>DWM: DwmSetWindowAttribute (DWMWA_COLOR_NONE)
Note over DWM: Border set before first paint

Note over Window,WB: Activation occurs
DWM-->>Window: WM_NCACTIVATE
Window->>WB: BorderHook (via HwndSource hook)
WB->>DWM: DwmSetWindowAttribute (synchronous)
Note over DWM: Border reapplied in same repaint cycle

Note over Window,WB: Safety net at ApplicationIdle
Window->>WB: Apply (dispatched)
WB->>DWM: DwmSetWindowAttribute
Loading

🌟 Strengths

  • Clear, well-documented fix that addresses the root cause of the flash.
  • Low regression risk: changes are limited to timing of existing functionality.

💡 Suggestions (P2)

  • src/RdpManager/WindowBorder.cs: Each call to Keep registers a duplicate BorderHook via HwndSource.AddHook, causing redundant DwmSetWindowAttribute calls on every WM_NCACTIVATE and slight performance overhead. Consider guarding against duplicates with an attached property or similar mechanism.

💡 Have feedback? We'd love to hear it in our GitHub Discussions.
✨ This review was generated by LlamaPReview Advanced, which is free for all open-source projects. Learn more.

@@ -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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants