diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 75599dda0e..b4354e2f91 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -156,6 +156,7 @@ builttoroam BVal BValue byapp +BYCOMMAND BYPOSITION CALCRECT CALG diff --git a/src/runner/bug_report.cpp b/src/runner/bug_report.cpp index 9abfe6fa18..697bf518f7 100644 --- a/src/runner/bug_report.cpp +++ b/src/runner/bug_report.cpp @@ -4,17 +4,52 @@ #include #include -std::atomic_bool isBugReportThreadRunning = false; +BugReportManager& BugReportManager::instance() +{ + static BugReportManager instance; + return instance; +} -void launch_bug_report() noexcept +void BugReportManager::register_callback(const BugReportCallback& callback) +{ + std::lock_guard lock(m_callbacksMutex); + m_callbacks.push_back(callback); +} + +void BugReportManager::clear_callbacks() +{ + std::lock_guard lock(m_callbacksMutex); + m_callbacks.clear(); +} + +void BugReportManager::notify_observers(bool isRunning) +{ + std::lock_guard lock(m_callbacksMutex); + for (const auto& callback : m_callbacks) + { + try + { + callback(isRunning); + } + catch (...) + { + // Ignore callback exceptions to prevent one bad callback from affecting others + } + } +} + +void BugReportManager::launch_bug_report() noexcept { std::wstring bug_report_path = get_module_folderpath(); bug_report_path += L"\\Tools\\PowerToys.BugReportTool.exe"; - bool expected_isBugReportThreadRunning = false; - if (isBugReportThreadRunning.compare_exchange_strong(expected_isBugReportThreadRunning, true)) + bool expected_isBugReportRunning = false; + if (m_isBugReportRunning.compare_exchange_strong(expected_isBugReportRunning, true)) { - std::thread([bug_report_path]() { + // Notify observers that bug report is starting + notify_observers(true); + + std::thread([this, bug_report_path]() { SHELLEXECUTEINFOW sei{ sizeof(sei) }; sei.fMask = { SEE_MASK_FLAG_NO_UI | SEE_MASK_NOASYNC | SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE }; sei.lpFile = bug_report_path.c_str(); @@ -27,7 +62,29 @@ void launch_bug_report() noexcept MessageBoxW(nullptr, bugreport_success.c_str(), L"PowerToys", MB_OK); } - isBugReportThreadRunning.store(false); + m_isBugReportRunning.store(false); + // Notify observers that bug report has finished + notify_observers(false); }).detach(); } + else + { + notify_observers(false); + } +} + +bool BugReportManager::is_bug_report_running() const noexcept +{ + return m_isBugReportRunning.load(); +} + +// Legacy functions for backward compatibility +void launch_bug_report() noexcept +{ + BugReportManager::instance().launch_bug_report(); +} + +bool is_bug_report_running() noexcept +{ + return BugReportManager::instance().is_bug_report_running(); } diff --git a/src/runner/bug_report.h b/src/runner/bug_report.h index 2d7084ea21..6edb1c6ba3 100644 --- a/src/runner/bug_report.h +++ b/src/runner/bug_report.h @@ -1,3 +1,43 @@ #pragma once -void launch_bug_report() noexcept; \ No newline at end of file +#include +#include +#include + +// Observer pattern for bug report status changes +using BugReportCallback = std::function; + +class BugReportManager +{ +public: + static BugReportManager& instance(); + + // Register a callback to be notified when bug report status changes + void register_callback(const BugReportCallback& callback); + + // Remove all callbacks (useful for cleanup) + void clear_callbacks(); + + // Launch bug report and notify observers + void launch_bug_report() noexcept; + + // Check if bug report is currently running + bool is_bug_report_running() const noexcept; + +private: + BugReportManager() = default; + ~BugReportManager() = default; + BugReportManager(const BugReportManager&) = delete; + BugReportManager& operator=(const BugReportManager&) = delete; + + // Notify all registered callbacks + void notify_observers(bool isRunning); + + std::atomic_bool m_isBugReportRunning = false; + std::vector m_callbacks; + mutable std::mutex m_callbacksMutex; +}; + +// Legacy functions for backward compatibility +void launch_bug_report() noexcept; +bool is_bug_report_running() noexcept; \ No newline at end of file diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp index 265d3e3dc0..966ea95163 100644 --- a/src/runner/settings_window.cpp +++ b/src/runner/settings_window.cpp @@ -227,6 +227,14 @@ void dispatch_received_json(const std::wstring& json_to_parse) { launch_bug_report(); } + else if (name == L"bug_report_status") + { + json::JsonObject result; + result.SetNamedValue(L"bug_report_running", winrt::Windows::Data::Json::JsonValue::CreateBooleanValue(is_bug_report_running())); + std::unique_lock lock{ ipc_mutex }; + if (current_settings_ipc) + current_settings_ipc->send(result.Stringify().c_str()); + } else if (name == L"killrunner") { const auto pt_main_window = FindWindowW(pt_tray_icon_window_class, nullptr); @@ -488,7 +496,18 @@ void run_settings_window(bool show_oobe_window, bool show_scoobe_window, std::op std::unique_lock lock{ ipc_mutex }; current_settings_ipc = new TwoWayPipeMessageIPC(powertoys_pipe_name, settings_pipe_name, receive_json_send_to_main_thread); current_settings_ipc->start(hToken); + + // Register callback for bug report status changes + BugReportManager::instance().register_callback([](bool isRunning) { + json::JsonObject result; + result.SetNamedValue(L"bug_report_running", winrt::Windows::Data::Json::JsonValue::CreateBooleanValue(isRunning)); + + std::unique_lock lock{ ipc_mutex }; + if (current_settings_ipc) + current_settings_ipc->send(result.Stringify().c_str()); + }); } + g_settings_process_id = process_info.dwProcessId; if (process_info.hProcess) diff --git a/src/runner/tray_icon.cpp b/src/runner/tray_icon.cpp index 30cca89951..7f819209e5 100644 --- a/src/runner/tray_icon.cpp +++ b/src/runner/tray_icon.cpp @@ -207,6 +207,8 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam change_menu_item_text(ID_EXIT_MENU_COMMAND, exit_menuitem_label.data()); change_menu_item_text(ID_SHOW_TRAY_ICON_MENU_COMMAND, show_tray_icon_menuitem_label.data()); change_menu_item_text(ID_REPORT_BUG_COMMAND, submit_bug_menuitem_label.data()); + bool bug_report_disabled = is_bug_report_running(); + EnableMenuItem(h_sub_menu, ID_REPORT_BUG_COMMAND, MF_BYCOMMAND | (bug_report_disabled ? MF_GRAYED : MF_ENABLED)); change_menu_item_text(ID_DOCUMENTATION_MENU_COMMAND, documentation_menuitem_label.data()); change_menu_item_text(ID_QUICK_ACCESS_MENU_COMMAND, quick_access_menuitem_label.data()); } @@ -269,6 +271,14 @@ LRESULT __stdcall tray_icon_window_proc(HWND window, UINT message, WPARAM wparam return DefWindowProc(window, message, wparam, lparam); } +void update_bug_report_menu_status(bool isRunning) +{ + if (h_sub_menu != nullptr) + { + EnableMenuItem(h_sub_menu, ID_REPORT_BUG_COMMAND, MF_BYCOMMAND | (isRunning ? MF_GRAYED : MF_ENABLED)); + } +} + void start_tray_icon(bool isProcessElevated) { auto h_instance = reinterpret_cast(&__ImageBase); @@ -319,6 +329,16 @@ void start_tray_icon(bool isProcessElevated) ChangeWindowMessageFilterEx(hwnd, WM_COMMAND, MSGFLT_ALLOW, nullptr); tray_icon_created = Shell_NotifyIcon(NIM_ADD, &tray_icon_data) == TRUE; + + // Register callback to update bug report menu item status + BugReportManager::instance().register_callback([](bool isRunning) { + dispatch_run_on_main_ui_thread([](PVOID data) { + bool* running = static_cast(data); + update_bug_report_menu_status(*running); + delete running; + }, + new bool(isRunning)); + }); } } @@ -334,6 +354,8 @@ void stop_tray_icon() { if (tray_icon_created) { + // Clear bug report callbacks + BugReportManager::instance().clear_callbacks(); SendMessage(tray_icon_hwnd, WM_CLOSE, 0, 0); } -} +} \ No newline at end of file diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml index 13d6ee5c8f..a80094e5a2 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Views/GeneralPage.xaml @@ -454,11 +454,25 @@