From f44e4882e5d34a83a87f7a7a855d4273d7445a43 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 May 2025 05:57:35 +0000 Subject: [PATCH 1/3] Initial plan for issue From 44802779ec373023402c52a8cf9cee8787c885ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 May 2025 06:03:24 +0000 Subject: [PATCH 2/3] Implement duplicate file activation detection Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com> --- dev/AppLifecycle/AppInstance.cpp | 123 +++++++++++++++++++++++++++++++ dev/AppLifecycle/AppInstance.h | 13 ++++ 2 files changed, 136 insertions(+) diff --git a/dev/AppLifecycle/AppInstance.cpp b/dev/AppLifecycle/AppInstance.cpp index 2b0d38ee0c..aca912cd21 100644 --- a/dev/AppLifecycle/AppInstance.cpp +++ b/dev/AppLifecycle/AppInstance.cpp @@ -181,6 +181,114 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation m_redirectionArgs.Enqueue(id); } + bool AppInstance::IsRecentFileActivation(Microsoft::Windows::AppLifecycle::AppActivationArguments const& args) + { + // Only check for duplicate file activations + if (args.Kind() != ExtendedActivationKind::File) + { + return false; + } + + try + { + auto data = args.Data().try_as(); + if (!data) + { + return false; + } + + // Get file paths from the activation + std::set newFilePaths; + auto files = data.Files(); + for (uint32_t i = 0; i < files.Size(); i++) + { + newFilePaths.insert(files.GetAt(i).Path()); + } + + // If no files, not a duplicate + if (newFilePaths.empty()) + { + return false; + } + + auto now = std::chrono::system_clock::now(); + + // Remove old activation records (older than 1 second) + auto releaseOnExit = m_dataMutex.acquire(); + auto it = m_recentFileActivations.begin(); + while (it != m_recentFileActivations.end()) + { + if (now - it->timestamp > std::chrono::seconds(1)) + { + it = m_recentFileActivations.erase(it); + } + else + { + ++it; + } + } + + // Check if this set of files matches a recent activation + for (const auto& recent : m_recentFileActivations) + { + // If file sets are the same, this is a duplicate activation + if (recent.filePaths == newFilePaths) + { + return true; + } + } + } + catch (...) + { + // If any exception occurs, assume it's not a duplicate + return false; + } + + return false; + } + + void AppInstance::RecordFileActivation(Microsoft::Windows::AppLifecycle::AppActivationArguments const& args) + { + if (args.Kind() != ExtendedActivationKind::File) + { + return; + } + + try + { + auto data = args.Data().try_as(); + if (!data) + { + return; + } + + // Get file paths from the activation + std::set filePaths; + auto files = data.Files(); + for (uint32_t i = 0; i < files.Size(); i++) + { + filePaths.insert(files.GetAt(i).Path()); + } + + // If no files, don't record + if (filePaths.empty()) + { + return; + } + + // Add to recent activations + auto releaseOnExit = m_dataMutex.acquire(); + m_recentFileActivations.push_back({ + std::chrono::system_clock::now(), + std::move(filePaths) + }); + } + catch (...) + { + // Ignore errors in recording + } + } + void AppInstance::ProcessRedirectionRequests() { m_innerActivated.ResetEvent(); @@ -197,6 +305,21 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation request.Open(name); auto args = request.UnmarshalArguments(); + // Skip this activation if it's a duplicate file activation that was recently processed + if (IsRecentFileActivation(args)) + { + std::wstring eventName = name + c_activatedEventNameSuffix; + wil::unique_event cleanupEvent; + if (cleanupEvent.try_open(eventName.c_str())) + { + cleanupEvent.SetEvent(); + } + continue; + } + + // Record this file activation to prevent duplicates + RecordFileActivation(args); + // Notify the app that the redirection request is here. m_activatedEvent(*this, args); diff --git a/dev/AppLifecycle/AppInstance.h b/dev/AppLifecycle/AppInstance.h index 57eb83ea7f..a618667eed 100644 --- a/dev/AppLifecycle/AppInstance.h +++ b/dev/AppLifecycle/AppInstance.h @@ -8,6 +8,9 @@ #include "RedirectionRequest.h" #include "SharedProcessList.h" #include "RedirectionRequestQueue.h" +#include +#include +#include namespace winrt::Microsoft::Windows::AppLifecycle::implementation { @@ -53,6 +56,8 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation Microsoft::Windows::AppLifecycle::AppInstance FindForKey(std::wstring const& key); void EnqueueRedirectionRequestId(GUID id); GUID DequeueRedirectionRequestId(); + bool IsRecentFileActivation(Microsoft::Windows::AppLifecycle::AppActivationArguments const& args); + void RecordFileActivation(Microsoft::Windows::AppLifecycle::AppActivationArguments const& args); // Named object prefixes used to scope. std::wstring m_moduleName; @@ -83,6 +88,14 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation SharedProcessList m_instances; RedirectionRequestQueue m_redirectionArgs; + + // Tracking structure for recent file activations to prevent duplicates + struct RecentFileActivation + { + std::chrono::system_clock::time_point timestamp; + std::set filePaths; + }; + std::vector m_recentFileActivations; }; } From 8b5c961d2e4021f64b8293d4093aee39e1433eea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 May 2025 06:04:33 +0000 Subject: [PATCH 3/3] Add detailed comments explaining file activation deduplication Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com> --- dev/AppLifecycle/AppInstance.cpp | 9 +++++++++ dev/AppLifecycle/AppInstance.h | 3 +++ 2 files changed, 12 insertions(+) diff --git a/dev/AppLifecycle/AppInstance.cpp b/dev/AppLifecycle/AppInstance.cpp index aca912cd21..f27dbbcff0 100644 --- a/dev/AppLifecycle/AppInstance.cpp +++ b/dev/AppLifecycle/AppInstance.cpp @@ -181,6 +181,10 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation m_redirectionArgs.Enqueue(id); } + // Checks if a file activation is a duplicate of a recent activation + // This is used to prevent duplicate activations when multiple files are opened at once + // When a user selects multiple files in Explorer and opens them, Windows may send multiple + // file activation events in quick succession, each containing the same set of files. bool AppInstance::IsRecentFileActivation(Microsoft::Windows::AppLifecycle::AppActivationArguments const& args) { // Only check for duplicate file activations @@ -214,6 +218,8 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation auto now = std::chrono::system_clock::now(); // Remove old activation records (older than 1 second) + // We only want to detect duplicates that happen in quick succession, + // which is the typical case for multi-file selection in Explorer auto releaseOnExit = m_dataMutex.acquire(); auto it = m_recentFileActivations.begin(); while (it != m_recentFileActivations.end()) @@ -247,6 +253,7 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation return false; } + // Records a file activation to prevent future duplicates void AppInstance::RecordFileActivation(Microsoft::Windows::AppLifecycle::AppActivationArguments const& args) { if (args.Kind() != ExtendedActivationKind::File) @@ -306,6 +313,8 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation auto args = request.UnmarshalArguments(); // Skip this activation if it's a duplicate file activation that was recently processed + // This prevents multiple activations when a user selects multiple files in Explorer + // and opens them all at once, which can cause Windows to send multiple activation events if (IsRecentFileActivation(args)) { std::wstring eventName = name + c_activatedEventNameSuffix; diff --git a/dev/AppLifecycle/AppInstance.h b/dev/AppLifecycle/AppInstance.h index a618667eed..a631f3c551 100644 --- a/dev/AppLifecycle/AppInstance.h +++ b/dev/AppLifecycle/AppInstance.h @@ -90,6 +90,9 @@ namespace winrt::Microsoft::Windows::AppLifecycle::implementation RedirectionRequestQueue m_redirectionArgs; // Tracking structure for recent file activations to prevent duplicates + // This is used to prevent multiple activations when a user selects multiple files + // in Explorer and opens them together, which can cause Windows to send multiple + // activation events, each containing the same set of files struct RecentFileActivation { std::chrono::system_clock::time_point timestamp;