#include "pch.h" #include "general_settings.h" #include "auto_start_helper.h" #include "tray_icon.h" #include "quick_access_host.h" #include "Generated files/resource.h" #include "hotkey_conflict_detector.h" #include #include "powertoy_module.h" #include #include "trace.h" #include "ai_detection.h" #include #include #include namespace { json::JsonValue create_empty_shortcut_array_value() { return json::JsonValue::Parse(L"[]"); } void ensure_ignored_conflict_properties_shape(json::JsonObject& obj) { if (!json::has(obj, L"ignored_shortcuts", json::JsonValueType::Array)) { obj.SetNamedValue(L"ignored_shortcuts", create_empty_shortcut_array_value()); } } json::JsonObject create_default_ignored_conflict_properties() { json::JsonObject obj; ensure_ignored_conflict_properties_shape(obj); return obj; } DashboardSortOrder parse_dashboard_sort_order(const json::JsonObject& obj, DashboardSortOrder fallback) { if (json::has(obj, L"dashboard_sort_order", json::JsonValueType::Number)) { const auto raw_value = static_cast(obj.GetNamedNumber(L"dashboard_sort_order", static_cast(static_cast(fallback)))); return raw_value == static_cast(DashboardSortOrder::ByStatus) ? DashboardSortOrder::ByStatus : DashboardSortOrder::Alphabetical; } if (json::has(obj, L"dashboard_sort_order", json::JsonValueType::String)) { const auto raw = obj.GetNamedString(L"dashboard_sort_order"); if (raw == L"ByStatus") { return DashboardSortOrder::ByStatus; } if (raw == L"Alphabetical") { return DashboardSortOrder::Alphabetical; } } return fallback; } } // TODO: would be nice to get rid of these globals, since they're basically cached json settings static std::wstring settings_theme = L"system"; static bool show_tray_icon = true; static bool show_theme_adaptive_tray_icon = false; static bool run_as_elevated = false; static bool show_new_updates_toast_notification = true; static bool download_updates_automatically = true; static bool show_whats_new_after_updates = true; static bool enable_experimentation = true; static bool enable_warnings_elevated_apps = true; static bool enable_quick_access = true; static PowerToysSettings::HotkeyObject quick_access_shortcut; static DashboardSortOrder dashboard_sort_order = DashboardSortOrder::Alphabetical; static json::JsonObject ignored_conflict_properties = create_default_ignored_conflict_properties(); json::JsonObject GeneralSettings::to_json() { json::JsonObject result; auto ignoredProps = ignoredConflictProperties; ensure_ignored_conflict_properties_shape(ignoredProps); result.SetNamedValue(L"startup", json::value(isStartupEnabled)); if (!startupDisabledReason.empty()) { result.SetNamedValue(L"startup_disabled_reason", json::value(startupDisabledReason)); } json::JsonObject enabled; for (const auto& [name, isEnabled] : isModulesEnabledMap) { enabled.SetNamedValue(name, json::value(isEnabled)); } result.SetNamedValue(L"enabled", std::move(enabled)); result.SetNamedValue(L"show_tray_icon", json::value(showSystemTrayIcon)); result.SetNamedValue(L"show_theme_adaptive_tray_icon", json::value(showThemeAdaptiveTrayIcon)); result.SetNamedValue(L"is_elevated", json::value(isElevated)); result.SetNamedValue(L"run_elevated", json::value(isRunElevated)); result.SetNamedValue(L"show_new_updates_toast_notification", json::value(showNewUpdatesToastNotification)); result.SetNamedValue(L"download_updates_automatically", json::value(downloadUpdatesAutomatically)); result.SetNamedValue(L"show_whats_new_after_updates", json::value(showWhatsNewAfterUpdates)); result.SetNamedValue(L"enable_experimentation", json::value(enableExperimentation)); result.SetNamedValue(L"dashboard_sort_order", json::value(static_cast(dashboardSortOrder))); result.SetNamedValue(L"is_admin", json::value(isAdmin)); result.SetNamedValue(L"enable_warnings_elevated_apps", json::value(enableWarningsElevatedApps)); result.SetNamedValue(L"enable_quick_access", json::value(enableQuickAccess)); result.SetNamedValue(L"quick_access_shortcut", quickAccessShortcut.get_json()); result.SetNamedValue(L"theme", json::value(theme)); result.SetNamedValue(L"system_theme", json::value(systemTheme)); result.SetNamedValue(L"powertoys_version", json::value(powerToysVersion)); result.SetNamedValue(L"ignored_conflict_properties", json::value(ignoredProps)); return result; } json::JsonObject load_general_settings() { auto loaded = PTSettingsHelper::load_general_settings(); settings_theme = loaded.GetNamedString(L"theme", L"system"); if (settings_theme != L"dark" && settings_theme != L"light") { settings_theme = L"system"; } show_tray_icon = loaded.GetNamedBoolean(L"show_tray_icon", true); show_theme_adaptive_tray_icon = loaded.GetNamedBoolean(L"show_theme_adaptive_tray_icon", false); run_as_elevated = loaded.GetNamedBoolean(L"run_elevated", false); show_new_updates_toast_notification = loaded.GetNamedBoolean(L"show_new_updates_toast_notification", true); download_updates_automatically = loaded.GetNamedBoolean(L"download_updates_automatically", true) && check_user_is_admin(); show_whats_new_after_updates = loaded.GetNamedBoolean(L"show_whats_new_after_updates", true); enable_experimentation = loaded.GetNamedBoolean(L"enable_experimentation", true); enable_warnings_elevated_apps = loaded.GetNamedBoolean(L"enable_warnings_elevated_apps", true); enable_quick_access = loaded.GetNamedBoolean(L"enable_quick_access", true); if (json::has(loaded, L"quick_access_shortcut", json::JsonValueType::Object)) { quick_access_shortcut = PowerToysSettings::HotkeyObject::from_json(loaded.GetNamedObject(L"quick_access_shortcut")); } dashboard_sort_order = parse_dashboard_sort_order(loaded, dashboard_sort_order); if (json::has(loaded, L"ignored_conflict_properties", json::JsonValueType::Object)) { ignored_conflict_properties = loaded.GetNamedObject(L"ignored_conflict_properties"); } else { ignored_conflict_properties = create_default_ignored_conflict_properties(); } ensure_ignored_conflict_properties_shape(ignored_conflict_properties); return loaded; } GeneralSettings get_general_settings() { const bool is_user_admin = check_user_is_admin(); GeneralSettings settings { .showSystemTrayIcon = show_tray_icon, .showThemeAdaptiveTrayIcon = show_theme_adaptive_tray_icon, .isElevated = is_process_elevated(), .isRunElevated = run_as_elevated, .isAdmin = is_user_admin, .enableWarningsElevatedApps = enable_warnings_elevated_apps, .enableQuickAccess = enable_quick_access, .quickAccessShortcut = quick_access_shortcut, .showNewUpdatesToastNotification = show_new_updates_toast_notification, .downloadUpdatesAutomatically = download_updates_automatically && is_user_admin, .showWhatsNewAfterUpdates = show_whats_new_after_updates, .enableExperimentation = enable_experimentation, .dashboardSortOrder = dashboard_sort_order, .theme = settings_theme, .systemTheme = WindowsColors::is_dark_mode() ? L"dark" : L"light", .powerToysVersion = get_product_version(), .ignoredConflictProperties = ignored_conflict_properties }; ensure_ignored_conflict_properties_shape(settings.ignoredConflictProperties); settings.isStartupEnabled = is_auto_start_task_active_for_this_user(); for (auto& [name, powertoy] : modules()) { settings.isModulesEnabledMap[name] = powertoy->is_enabled(); } return settings; } void apply_module_status_update(const json::JsonObject& module_config, bool save) { Logger::info(L"apply_module_status_update: {}", std::wstring{ module_config.ToString() }); // Expected format: {"ModuleName": true/false} - only one module per update auto iter = module_config.First(); if (!iter.HasCurrent()) { Logger::warn(L"apply_module_status_update: Empty module config"); return; } const auto& element = iter.Current(); const auto value = element.Value(); if (value.ValueType() != json::JsonValueType::Boolean) { Logger::warn(L"apply_module_status_update: Invalid value type for module status"); return; } const std::wstring name{ element.Key().c_str() }; if (modules().find(name) == modules().end()) { Logger::warn(L"apply_module_status_update: Module {} not found", name); return; } PowertoyModule& powertoy = modules().at(name); const bool module_inst_enabled = powertoy->is_enabled(); bool target_enabled = value.GetBoolean(); auto gpo_rule = powertoy->gpo_policy_enabled_configuration(); if (gpo_rule == powertoys_gpo::gpo_rule_configured_enabled || gpo_rule == powertoys_gpo::gpo_rule_configured_disabled) { // Apply the GPO Rule. target_enabled = gpo_rule == powertoys_gpo::gpo_rule_configured_enabled; } if (module_inst_enabled == target_enabled) { Logger::info(L"apply_module_status_update: Module {} already in target state {}", name, target_enabled); return; } if (target_enabled) { Logger::info(L"apply_module_status_update: Enabling powertoy {}", name); powertoy->enable(); auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance(); hkmng.EnableHotkeyByModule(name); // Trigger AI capability detection when ImageResizer is enabled if (name == L"Image Resizer") { Logger::info(L"ImageResizer enabled, triggering AI capability detection"); DetectAiCapabilitiesAsync(true); // Skip settings check since we know it's being enabled } } else { Logger::info(L"apply_module_status_update: Disabling powertoy {}", name); powertoy->disable(); auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance(); hkmng.DisableHotkeyByModule(name); } // Sync the hotkey state with the module state, so it can be removed for disabled modules. powertoy.UpdateHotkeyEx(); if (save) { // Load existing settings and only update the specific module's enabled state json::JsonObject current_settings = PTSettingsHelper::load_general_settings(); json::JsonObject enabled; if (current_settings.HasKey(L"enabled")) { enabled = current_settings.GetNamedObject(L"enabled"); } // Check if the saved state is different from the requested state bool current_saved = enabled.HasKey(name) ? enabled.GetNamedBoolean(name, true) : true; if (current_saved != target_enabled) { // Update only this module's enabled state enabled.SetNamedValue(name, json::value(target_enabled)); current_settings.SetNamedValue(L"enabled", enabled); PTSettingsHelper::save_general_settings(current_settings); GeneralSettings settings_for_trace = get_general_settings(); Trace::SettingsChanged(settings_for_trace); } } } void apply_general_settings(const json::JsonObject& general_configs, bool save) { std::wstring old_settings_json_string; if (save) { old_settings_json_string = get_general_settings().to_json().Stringify().c_str(); } Logger::info(L"apply_general_settings: {}", std::wstring{ general_configs.ToString() }); run_as_elevated = general_configs.GetNamedBoolean(L"run_elevated", false); enable_warnings_elevated_apps = general_configs.GetNamedBoolean(L"enable_warnings_elevated_apps", true); bool new_enable_quick_access = general_configs.GetNamedBoolean(L"enable_quick_access", true); Logger::info(L"apply_general_settings: enable_quick_access={}, new_enable_quick_access={}", enable_quick_access, new_enable_quick_access); PowerToysSettings::HotkeyObject new_quick_access_shortcut; if (json::has(general_configs, L"quick_access_shortcut", json::JsonValueType::Object)) { new_quick_access_shortcut = PowerToysSettings::HotkeyObject::from_json(general_configs.GetNamedObject(L"quick_access_shortcut")); } auto hotkey_equals = [](const PowerToysSettings::HotkeyObject& a, const PowerToysSettings::HotkeyObject& b) { return a.get_code() == b.get_code() && a.get_modifiers() == b.get_modifiers(); }; if (enable_quick_access != new_enable_quick_access || !hotkey_equals(quick_access_shortcut, new_quick_access_shortcut)) { enable_quick_access = new_enable_quick_access; quick_access_shortcut = new_quick_access_shortcut; if (enable_quick_access) { QuickAccessHost::start(); } else { QuickAccessHost::stop(); } update_quick_access_hotkey(enable_quick_access, quick_access_shortcut); } show_new_updates_toast_notification = general_configs.GetNamedBoolean(L"show_new_updates_toast_notification", true); download_updates_automatically = general_configs.GetNamedBoolean(L"download_updates_automatically", true); show_whats_new_after_updates = general_configs.GetNamedBoolean(L"show_whats_new_after_updates", true); enable_experimentation = general_configs.GetNamedBoolean(L"enable_experimentation", true); dashboard_sort_order = parse_dashboard_sort_order(general_configs, dashboard_sort_order); // apply_general_settings is called by the runner's WinMain, so we can just force the run at startup gpo rule here. auto gpo_run_as_startup = powertoys_gpo::getConfiguredRunAtStartupValue(); if (json::has(general_configs, L"startup", json::JsonValueType::Boolean)) { bool startup = general_configs.GetNamedBoolean(L"startup"); if (gpo_run_as_startup == powertoys_gpo::gpo_rule_configured_enabled) { startup = true; } else if (gpo_run_as_startup == powertoys_gpo::gpo_rule_configured_disabled) { startup = false; } if (startup) { if (is_process_elevated()) { delete_auto_start_task_for_this_user(); create_auto_start_task_for_this_user(run_as_elevated); } else { if (!is_auto_start_task_active_for_this_user()) { delete_auto_start_task_for_this_user(); create_auto_start_task_for_this_user(false); run_as_elevated = false; } else if (!general_configs.GetNamedBoolean(L"run_elevated", false)) { delete_auto_start_task_for_this_user(); create_auto_start_task_for_this_user(false); } } } else { delete_auto_start_task_for_this_user(); } } else { delete_auto_start_task_for_this_user(); if (gpo_run_as_startup == powertoys_gpo::gpo_rule_configured_enabled || gpo_run_as_startup == powertoys_gpo::gpo_rule_configured_not_configured) { create_auto_start_task_for_this_user(run_as_elevated); } } if (json::has(general_configs, L"enabled")) { for (const auto& enabled_element : general_configs.GetNamedObject(L"enabled")) { const auto value = enabled_element.Value(); if (value.ValueType() != json::JsonValueType::Boolean) { continue; } const std::wstring name{ enabled_element.Key().c_str() }; const bool found = modules().find(name) != modules().end(); if (!found) { continue; } PowertoyModule& powertoy = modules().at(name); const bool module_inst_enabled = powertoy->is_enabled(); bool target_enabled = value.GetBoolean(); auto gpo_rule = powertoy->gpo_policy_enabled_configuration(); if (gpo_rule == powertoys_gpo::gpo_rule_configured_enabled || gpo_rule == powertoys_gpo::gpo_rule_configured_disabled) { // Apply the GPO Rule. target_enabled = gpo_rule == powertoys_gpo::gpo_rule_configured_enabled; } if (module_inst_enabled == target_enabled) { continue; } if (target_enabled) { Logger::info(L"apply_general_settings: Enabling powertoy {}", name); powertoy->enable(); auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance(); hkmng.EnableHotkeyByModule(name); // Trigger AI capability detection when ImageResizer is enabled if (name == L"Image Resizer") { Logger::info(L"ImageResizer enabled, triggering AI capability detection"); DetectAiCapabilitiesAsync(true); // Skip settings check since we know it's being enabled } } else { Logger::info(L"apply_general_settings: Disabling powertoy {}", name); powertoy->disable(); auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance(); hkmng.DisableHotkeyByModule(name); } // Sync the hotkey state with the module state, so it can be removed for disabled modules. powertoy.UpdateHotkeyEx(); } } if (json::has(general_configs, L"theme", json::JsonValueType::String)) { settings_theme = general_configs.GetNamedString(L"theme"); } if (json::has(general_configs, L"show_tray_icon", json::JsonValueType::Boolean)) { show_tray_icon = general_configs.GetNamedBoolean(L"show_tray_icon"); set_tray_icon_visible(show_tray_icon); } if (json::has(general_configs, L"show_theme_adaptive_tray_icon", json::JsonValueType::Boolean)) { bool new_theme_adaptive = general_configs.GetNamedBoolean(L"show_theme_adaptive_tray_icon"); Logger::info(L"apply_general_settings: show_theme_adaptive_tray_icon current={}, new={}", show_theme_adaptive_tray_icon, new_theme_adaptive); if (show_theme_adaptive_tray_icon != new_theme_adaptive) { show_theme_adaptive_tray_icon = new_theme_adaptive; set_tray_icon_theme_adaptive(show_theme_adaptive_tray_icon); } else { Logger::info(L"apply_general_settings: show_theme_adaptive_tray_icon unchanged, skipping update"); } } else { Logger::warn(L"apply_general_settings: show_theme_adaptive_tray_icon not found in config"); } if (json::has(general_configs, L"ignored_conflict_properties", json::JsonValueType::Object)) { ignored_conflict_properties = general_configs.GetNamedObject(L"ignored_conflict_properties"); ensure_ignored_conflict_properties_shape(ignored_conflict_properties); } if (save) { GeneralSettings save_settings = get_general_settings(); std::wstring new_settings_json_string = save_settings.to_json().Stringify().c_str(); if (old_settings_json_string != new_settings_json_string) { PTSettingsHelper::save_general_settings(save_settings.to_json()); Trace::SettingsChanged(save_settings); } } } void start_enabled_powertoys() { std::unordered_set powertoys_to_disable; std::unordered_map powertoys_gpo_configuration; // Take into account default values supplied by modules themselves and gpo configurations for (auto& [name, powertoy] : modules()) { auto gpo_rule = powertoy->gpo_policy_enabled_configuration(); powertoys_gpo_configuration[name] = gpo_rule; if (gpo_rule == powertoys_gpo::gpo_rule_configured_unavailable) { Logger::warn(L"start_enabled_powertoys: couldn't read the gpo rule for Powertoy {}", name); } if (gpo_rule == powertoys_gpo::gpo_rule_configured_wrong_value) { Logger::warn(L"start_enabled_powertoys: gpo rule for Powertoy {} is set to an unknown value", name); } if (!powertoy->is_enabled_by_default()) powertoys_to_disable.emplace(name); } json::JsonObject general_settings; try { general_settings = load_general_settings(); if (general_settings.HasKey(L"enabled")) { json::JsonObject enabled = general_settings.GetNamedObject(L"enabled"); for (const auto& disabled_element : enabled) { std::wstring disable_module_name{ static_cast(disabled_element.Key()) }; if (powertoys_gpo_configuration.find(disable_module_name) != powertoys_gpo_configuration.end() && (powertoys_gpo_configuration[disable_module_name] == powertoys_gpo::gpo_rule_configured_enabled || powertoys_gpo_configuration[disable_module_name] == powertoys_gpo::gpo_rule_configured_disabled)) { // If gpo forces the enabled setting, no need to check the setting for this PowerToy. It will be applied later on this function. continue; } // Disable explicitly disabled modules if (!disabled_element.Value().GetBoolean()) { Logger::info(L"start_enabled_powertoys: Powertoy {} explicitly disabled", disable_module_name); powertoys_to_disable.emplace(std::move(disable_module_name)); } // If module was scheduled for disable, but it's enabled in the settings - override default value else if (auto it = powertoys_to_disable.find(disable_module_name); it != end(powertoys_to_disable)) { Logger::info(L"start_enabled_powertoys: Overriding default enabled value for {} powertoy", disable_module_name); powertoys_to_disable.erase(it); } } } } catch (...) { } for (auto& [name, powertoy] : modules()) { bool should_powertoy_be_enabled = true; auto gpo_rule = powertoys_gpo_configuration.find(name) != powertoys_gpo_configuration.end() ? powertoys_gpo_configuration[name] : powertoys_gpo::gpo_rule_configured_not_configured; if (gpo_rule == powertoys_gpo::gpo_rule_configured_enabled || gpo_rule == powertoys_gpo::gpo_rule_configured_disabled) { // Apply the GPO Rule. should_powertoy_be_enabled = gpo_rule == powertoys_gpo::gpo_rule_configured_enabled; Logger::info(L"start_enabled_powertoys: GPO sets the enabled state for {} powertoy as {}", name, should_powertoy_be_enabled); } else if (powertoys_to_disable.contains(name)) { // Apply the settings or default information provided by the PowerToy on first run. should_powertoy_be_enabled = false; } if (should_powertoy_be_enabled) { Logger::info(L"start_enabled_powertoys: Enabling powertoy {}", name); powertoy->enable(); auto& hkmng = HotkeyConflictDetector::HotkeyConflictManager::GetInstance(); hkmng.EnableHotkeyByModule(name); powertoy.UpdateHotkeyEx(); } } }