22#include " App.xaml.h"
33#include " MainWindow.xaml.h"
44#include " UI/Shell/NotifyIconContextMenu.xaml.h"
5+ #include " UI/Xaml/View/Dialog/CloseToTrayDialog.h"
56#include " Helpers/WindowHelper.h"
67#include " Helpers/ThemeHelper.h"
78#include " Core/P2PManager.h"
9+ #include " Core/DownloadManager.h"
810#include " Core/RSS/RSSManager.h"
911#include " Core/GeoIP/GeoIPManager.h"
1012
1113#include < winrt/Windows.ApplicationModel.Activation.h>
14+ #include < winrt/Microsoft.Windows.Storage.h>
15+ #include < winrt/Microsoft.UI.Xaml.Controls.h>
1216#include < sentry.h>
1317
1418using namespace winrt ;
@@ -60,23 +64,26 @@ namespace winrt::OpenNet::implementation
6064 trayIcon = OpenNet::UI::Shell::NotifyIconContextMenu ();
6165 trayIcon.Show ();
6266
63- // Register window closing event - hide to tray instead of closing
67+ // Register window closing event - close strategy ( hide to tray / ask / exit)
6468 window.AppWindow ().Closing ([](auto const &, winrt::Microsoft::UI ::Windowing::AppWindowClosingEventArgs const & args)
6569 {
6670 // If we are in an intentional exit, allow the window to close
6771 if (App::s_isExiting)
6872 return ;
6973
70- // Cancel the close and hide to tray instead
74+ // If no tray icon was created, allow direct close
75+ if (!App::trayIcon)
76+ return ;
77+
78+ // Cancel the close; the async strategy decides what happens next
7179 args.Cancel (true );
7280
73- // Hide the window
74- if (App::window)
75- {
76- App::window.AppWindow ().Hide ();
77- }
81+ // Prevent re-entrance
82+ if (App::s_isHandlingClose)
83+ return ;
84+ App::s_isHandlingClose = true ;
7885
79- OutputDebugStringA ( " App: MainWindow hidden to tray \n " );
86+ HandleCloseStrategyAsync ( );
8087 });
8188
8289 // Initialize RSS Manager early so feeds update in the background
@@ -203,11 +210,14 @@ namespace winrt::OpenNet::implementation
203210 trayIcon = nullptr ;
204211 }
205212
213+ // Stop RSS background updates
214+ ::OpenNet::Core::RSS::RSSManager::Instance ().Stop();
215+
206216 // Shutdown P2PManager
207217 ::OpenNet::Core::P2PManager::Instance ().Shutdown();
208218
209- // Stop RSS background updates
210- ::OpenNet::Core::RSS::RSSManager:: Instance ().Stop ();
219+ // Shutdown DownloadManager (Aria2 + refresh thread)
220+ ::OpenNet::Core::DownloadManager:: Instance ().Shutdown ();
211221
212222 // make sure everything flushes
213223 sentry_close ();
@@ -223,6 +233,102 @@ namespace winrt::OpenNet::implementation
223233 }
224234 }
225235
236+ winrt::fire_and_forget App::HandleCloseStrategyAsync ()
237+ {
238+ try
239+ {
240+ auto localSettings = winrt::Microsoft::Windows::Storage::ApplicationData::GetDefault ().LocalSettings ();
241+ auto values = localSettings.Values ();
242+
243+ // Strategy A: user already chose, don't ask again
244+ if (values.HasKey (L" Hide2TrayWhenCloseAsked" ))
245+ {
246+ bool asked = unbox_value<bool >(values.Lookup (L" Hide2TrayWhenCloseAsked" ));
247+ if (asked)
248+ {
249+ bool hide = false ;
250+ if (values.HasKey (L" Hide2TrayWhenClose" ))
251+ hide = unbox_value<bool >(values.Lookup (L" Hide2TrayWhenClose" ));
252+
253+ if (hide)
254+ HideToTray ();
255+ else
256+ ReallyClose ();
257+
258+ s_isHandlingClose = false ;
259+ co_return ;
260+ }
261+ }
262+
263+ // Strategy B: first time — show dialog
264+ if (!window)
265+ {
266+ ReallyClose ();
267+ s_isHandlingClose = false ;
268+ co_return ;
269+ }
270+
271+ auto content = window.Content ();
272+ if (!content)
273+ {
274+ ReallyClose ();
275+ s_isHandlingClose = false ;
276+ co_return ;
277+ }
278+
279+ auto xamlRoot = content.XamlRoot ();
280+ if (!xamlRoot)
281+ {
282+ ReallyClose ();
283+ s_isHandlingClose = false ;
284+ co_return ;
285+ }
286+
287+ auto dlg = winrt::OpenNet::UI::Xaml::View::Dialog::CloseToTrayDialog ();
288+ dlg.XamlRoot (xamlRoot);
289+
290+ auto result = co_await dlg.ShowAsync ();
291+
292+ // Save preference if "remember" was checked
293+ if (dlg.RememberChoice ())
294+ {
295+ values.Insert (L" Hide2TrayWhenCloseAsked" , box_value (true ));
296+ values.Insert (L" Hide2TrayWhenClose" , box_value (result == winrt::Microsoft::UI ::Xaml::Controls::ContentDialogResult::Primary));
297+ }
298+
299+ if (result == winrt::Microsoft::UI ::Xaml::Controls::ContentDialogResult::Primary)
300+ {
301+ HideToTray ();
302+ }
303+ else
304+ {
305+ ReallyClose ();
306+ }
307+ }
308+ catch (...)
309+ {
310+ OutputDebugStringA (" App: HandleCloseStrategyAsync error, falling back to exit\n " );
311+ ReallyClose ();
312+ }
313+
314+ s_isHandlingClose = false ;
315+ }
316+
317+ void App::HideToTray ()
318+ {
319+ if (window)
320+ {
321+ window.AppWindow ().Hide ();
322+ }
323+ OutputDebugStringA (" App: MainWindow hidden to tray\n " );
324+ }
325+
326+ void App::ReallyClose ()
327+ {
328+ s_isExiting = true ;
329+ Microsoft::UI::Xaml::Application::Current ().Exit ();
330+ }
331+
226332 winrt::fire_and_forget App::InitializeRSSManagerAsync ()
227333 {
228334 try
0 commit comments