From 5da184e55468a384f17cd25b2fca07ecba895079 Mon Sep 17 00:00:00 2001 From: Deyan Nenov Date: Tue, 24 Jun 2025 18:14:09 +0100 Subject: [PATCH 1/7] notification focus fix - fix to the notification popup window to allow Dynamo window to be closed correctly (without having to hit 'X' multiple times) --- src/Notifications/View/NotificationUI.xaml | 107 +++++++++--------- src/Notifications/View/NotificationUI.xaml.cs | 31 +++++ 2 files changed, 86 insertions(+), 52 deletions(-) diff --git a/src/Notifications/View/NotificationUI.xaml b/src/Notifications/View/NotificationUI.xaml index f3110fca342..5c075231e52 100644 --- a/src/Notifications/View/NotificationUI.xaml +++ b/src/Notifications/View/NotificationUI.xaml @@ -1,79 +1,82 @@ - + - - + + - - + + - - + - + - + - + - - + - + - + - - + + - + - - + + diff --git a/src/Notifications/View/NotificationUI.xaml.cs b/src/Notifications/View/NotificationUI.xaml.cs index eb7b8c4092a..a016dd1c5ce 100644 --- a/src/Notifications/View/NotificationUI.xaml.cs +++ b/src/Notifications/View/NotificationUI.xaml.cs @@ -1,6 +1,10 @@ +using System; using System.Reflection; +using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls.Primitives; +using System.Windows.Interop; +using System.Windows.Threading; namespace Dynamo.Notifications.View { @@ -40,6 +44,8 @@ public NotificationUI() mainPopupGrid.Width = notificationsUIViewModel.PopupRectangleWidth; mainPopupGrid.Height = notificationsUIViewModel.PopupRectangleHeight; mainPopupGrid.Margin = new Thickness(notificationsUIViewModel.PopupBordersOffSet, notificationsUIViewModel.PopupBordersOffSet, 0, 0); + + } internal void UpdatePopupLocation() @@ -60,5 +66,30 @@ internal void UpdatePopupSize() this.Height = notificationsUIViewModel.PopupRectangleHeight + notificationsUIViewModel.PopupBordersOffSet + 10; } + + // Wait until the Popup is opened to set focus back to the main window. + // This is necessary because Popup opens asynchronously, and WebView2 (HWND-based) + // can steal focus. We explicitly reclaim native focus to ensure correct interaction + // with the main window (e.g., close button, click handling). + private void NotificationUI_Opened(object sender, EventArgs e) + { + Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, () => + { + ForceMainWindowFocus(); + }); + } + + private void ForceMainWindowFocus() + { + ReleaseCapture(); + SetForegroundWindow(new WindowInteropHelper(Application.Current.MainWindow).Handle); + } + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetForegroundWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern bool ReleaseCapture(); } } From ab7c5bfaf0c81fa401e0b4ce0dd83fc5853b0554 Mon Sep 17 00:00:00 2001 From: Deyan Nenov Date: Wed, 25 Jun 2025 12:38:56 +0100 Subject: [PATCH 2/7] fix failing smoke test - potential race condition to execute refocus during Dynamo window tear down (not sure why the notification popup would be involved, but possibly linked) --- src/Notifications/View/NotificationUI.xaml.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Notifications/View/NotificationUI.xaml.cs b/src/Notifications/View/NotificationUI.xaml.cs index a016dd1c5ce..9d5a71c4d87 100644 --- a/src/Notifications/View/NotificationUI.xaml.cs +++ b/src/Notifications/View/NotificationUI.xaml.cs @@ -73,10 +73,21 @@ internal void UpdatePopupSize() // with the main window (e.g., close button, click handling). private void NotificationUI_Opened(object sender, EventArgs e) { - Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, () => + // Ensure the application and main window are available and fully loaded. + // This prevents trying to set focus on a window that is not ready or is shutting down. + if (Application.Current?.MainWindow is Window main && main.IsLoaded) { - ForceMainWindowFocus(); - }); + // If we're already on the UI thread, just call the focus method directly. + if (Dispatcher.CheckAccess()) + { + ForceMainWindowFocus(); + } + else + { + // Otherwise, invoke the call synchronously on the UI thread. + Dispatcher.Invoke(ForceMainWindowFocus); + } + } } private void ForceMainWindowFocus() From 3f109a6eeff5289bbfa373c2a8d30b49a8fc40e0 Mon Sep 17 00:00:00 2001 From: Deyan Nenov Date: Wed, 25 Jun 2025 13:14:57 +0100 Subject: [PATCH 3/7] test if changes are responsible for smoke test failure --- src/Notifications/View/NotificationUI.xaml.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Notifications/View/NotificationUI.xaml.cs b/src/Notifications/View/NotificationUI.xaml.cs index 9d5a71c4d87..f9263669f30 100644 --- a/src/Notifications/View/NotificationUI.xaml.cs +++ b/src/Notifications/View/NotificationUI.xaml.cs @@ -78,15 +78,15 @@ private void NotificationUI_Opened(object sender, EventArgs e) if (Application.Current?.MainWindow is Window main && main.IsLoaded) { // If we're already on the UI thread, just call the focus method directly. - if (Dispatcher.CheckAccess()) - { - ForceMainWindowFocus(); - } - else - { - // Otherwise, invoke the call synchronously on the UI thread. - Dispatcher.Invoke(ForceMainWindowFocus); - } + //if (Dispatcher.CheckAccess()) + //{ + // ForceMainWindowFocus(); + //} + //else + //{ + // // Otherwise, invoke the call synchronously on the UI thread. + // Dispatcher.Invoke(ForceMainWindowFocus); + //} } } From 92f7c94286e14a64daef45cc3ce6e991f576a40a Mon Sep 17 00:00:00 2001 From: Deyan Nenov Date: Wed, 25 Jun 2025 13:55:04 +0100 Subject: [PATCH 4/7] sync call focus - testing if synchronous call might be better for the smoke test --- src/Notifications/View/NotificationUI.xaml.cs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Notifications/View/NotificationUI.xaml.cs b/src/Notifications/View/NotificationUI.xaml.cs index f9263669f30..e8d0567983f 100644 --- a/src/Notifications/View/NotificationUI.xaml.cs +++ b/src/Notifications/View/NotificationUI.xaml.cs @@ -75,19 +75,20 @@ private void NotificationUI_Opened(object sender, EventArgs e) { // Ensure the application and main window are available and fully loaded. // This prevents trying to set focus on a window that is not ready or is shutting down. - if (Application.Current?.MainWindow is Window main && main.IsLoaded) - { - // If we're already on the UI thread, just call the focus method directly. - //if (Dispatcher.CheckAccess()) - //{ - // ForceMainWindowFocus(); - //} - //else - //{ - // // Otherwise, invoke the call synchronously on the UI thread. - // Dispatcher.Invoke(ForceMainWindowFocus); - //} - } + ForceMainWindowFocus(); + //if (Application.Current?.MainWindow is Window main && main.IsLoaded) + //{ + // // If we're already on the UI thread, just call the focus method directly. + // if (Dispatcher.CheckAccess()) + // { + // ForceMainWindowFocus(); + // } + // else + // { + // // Otherwise, invoke the call synchronously on the UI thread. + // Dispatcher.Invoke(ForceMainWindowFocus); + // } + //} } private void ForceMainWindowFocus() From 1332b47d4a6caa21c961d5bcf5633e434c70d756 Mon Sep 17 00:00:00 2001 From: Deyan Nenov Date: Wed, 25 Jun 2025 15:33:37 +0100 Subject: [PATCH 5/7] clean-up - clean up alternative code --- src/Notifications/View/NotificationUI.xaml.cs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/Notifications/View/NotificationUI.xaml.cs b/src/Notifications/View/NotificationUI.xaml.cs index e8d0567983f..37b64064596 100644 --- a/src/Notifications/View/NotificationUI.xaml.cs +++ b/src/Notifications/View/NotificationUI.xaml.cs @@ -74,21 +74,7 @@ internal void UpdatePopupSize() private void NotificationUI_Opened(object sender, EventArgs e) { // Ensure the application and main window are available and fully loaded. - // This prevents trying to set focus on a window that is not ready or is shutting down. ForceMainWindowFocus(); - //if (Application.Current?.MainWindow is Window main && main.IsLoaded) - //{ - // // If we're already on the UI thread, just call the focus method directly. - // if (Dispatcher.CheckAccess()) - // { - // ForceMainWindowFocus(); - // } - // else - // { - // // Otherwise, invoke the call synchronously on the UI thread. - // Dispatcher.Invoke(ForceMainWindowFocus); - // } - //} } private void ForceMainWindowFocus() From 551b3a3a4a8e241a90faa5dfd62a9c3e5647cdae Mon Sep 17 00:00:00 2001 From: Deyan Nenov Date: Wed, 25 Jun 2025 17:26:32 +0100 Subject: [PATCH 6/7] test fail fix - fixed test failing when no application is found during headless test execution --- src/Notifications/View/NotificationUI.xaml.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Notifications/View/NotificationUI.xaml.cs b/src/Notifications/View/NotificationUI.xaml.cs index 37b64064596..5ce7d60e34e 100644 --- a/src/Notifications/View/NotificationUI.xaml.cs +++ b/src/Notifications/View/NotificationUI.xaml.cs @@ -79,6 +79,9 @@ private void NotificationUI_Opened(object sender, EventArgs e) private void ForceMainWindowFocus() { + if (Application.Current?.MainWindow == null) + return; + ReleaseCapture(); SetForegroundWindow(new WindowInteropHelper(Application.Current.MainWindow).Handle); } From e5e1e6450fd9c4e108d7474f1836853c4b60a98b Mon Sep 17 00:00:00 2001 From: Deyan Nenov Date: Mon, 30 Jun 2025 09:42:44 +0100 Subject: [PATCH 7/7] capture errors for native calls - added SetLastError to true - surfacing the errors --- src/Notifications/View/NotificationUI.xaml.cs | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/Notifications/View/NotificationUI.xaml.cs b/src/Notifications/View/NotificationUI.xaml.cs index 5ce7d60e34e..56c44600c38 100644 --- a/src/Notifications/View/NotificationUI.xaml.cs +++ b/src/Notifications/View/NotificationUI.xaml.cs @@ -82,15 +82,25 @@ private void ForceMainWindowFocus() if (Application.Current?.MainWindow == null) return; - ReleaseCapture(); - SetForegroundWindow(new WindowInteropHelper(Application.Current.MainWindow).Handle); + if (!ReleaseCapture()) + { + int errorCode = Marshal.GetLastWin32Error(); + Console.WriteLine($"ReleaseCapture failed with error code: {errorCode}"); + } + + if (!SetForegroundWindow(new WindowInteropHelper(Application.Current.MainWindow).Handle)) + { + int errorCode = Marshal.GetLastWin32Error(); + Console.WriteLine($"SetForegroundWindow failed with error code: {errorCode}"); + } } - [DllImport("user32.dll")] + [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool SetForegroundWindow(IntPtr hWnd); - - [DllImport("user32.dll")] private static extern bool ReleaseCapture(); + + [DllImport("user32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetForegroundWindow(IntPtr hWnd); } }