diff --git a/.pipelines/tsa.json b/.pipelines/tsa.json index 351545613f..2f1e84c7f1 100644 --- a/.pipelines/tsa.json +++ b/.pipelines/tsa.json @@ -3,5 +3,5 @@ "notificationAliases": ["powertoys@microsoft.com"], "instanceUrl": "https://microsoft.visualstudio.com", "projectName": "OS", - "areaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\SHINE\\PowerToys" + "areaPath": "OS\\Windows Client and Services\\WinPD\\DEEP-Developer Experience, Ecosystem and Partnerships\\DIVE\\PowerToys" } diff --git a/src/modules/cmdpal/CmdPalModuleInterface/CmdPalModuleInterface.vcxproj b/src/modules/cmdpal/CmdPalModuleInterface/CmdPalModuleInterface.vcxproj index 5bd12f316e..2d70013009 100644 --- a/src/modules/cmdpal/CmdPalModuleInterface/CmdPalModuleInterface.vcxproj +++ b/src/modules/cmdpal/CmdPalModuleInterface/CmdPalModuleInterface.vcxproj @@ -2,7 +2,7 @@ - + 17.0 Win32Proj @@ -53,7 +53,6 @@ EXAMPLEPOWERTOY_EXPORTS;_WINDOWS;_USRDLL; %(PreprocessorDefinitions); - $(CommandPaletteBranding) IS_DEV_BRANDING;%(PreprocessorDefinitions) diff --git a/src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp b/src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp index be3eb6a3b7..bff7279b68 100644 --- a/src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp +++ b/src/modules/cmdpal/CmdPalModuleInterface/dllmain.cpp @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -10,10 +11,11 @@ #include #include #include +#include #include #include #include -#include +#include HINSTANCE g_hInst_cmdPal = 0; @@ -37,8 +39,6 @@ BOOL APIENTRY DllMain(HMODULE hInstance, class CmdPal : public PowertoyModuleIface { private: - bool m_enabled = false; - std::wstring app_name; //contains the non localized key of the powertoy @@ -46,7 +46,10 @@ private: HANDLE m_hTerminateEvent; - void LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated) + // Track if this is the first call to enable + bool firstEnableCall = true; + + static bool LaunchApp(const std::wstring& appPath, const std::wstring& commandLineArgs, bool elevated, bool silentFail) { std::wstring dir = std::filesystem::path(appPath).parent_path(); @@ -54,6 +57,10 @@ private: sei.cbSize = sizeof(SHELLEXECUTEINFO); sei.hwnd = nullptr; sei.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE; + if (silentFail) + { + sei.fMask = sei.fMask | SEE_MASK_FLAG_NO_UI; + } sei.lpVerb = elevated ? L"runas" : L"open"; sei.lpFile = appPath.c_str(); sei.lpParameters = commandLineArgs.c_str(); @@ -64,7 +71,11 @@ private: { std::wstring error = get_last_error_or_default(GetLastError()); Logger::error(L"Failed to launch process. {}", error); + return false; } + + m_launched.store(true); + return true; } std::vector GetProcessesIdByName(const std::wstring& processName) @@ -122,6 +133,9 @@ private: } public: + static std::atomic m_enabled; + static std::atomic m_launched; + CmdPal() { app_name = L"CmdPal"; @@ -133,10 +147,7 @@ public: ~CmdPal() { - if (m_enabled) - { - } - m_enabled = false; + CmdPal::m_enabled.store(false); } // Destroy the powertoy and free memory @@ -203,15 +214,18 @@ public: { Logger::trace("CmdPal::enable()"); - m_enabled = true; + CmdPal::m_enabled.store(true); - try - { - std::wstring packageName = L"Microsoft.CommandPalette"; + std::wstring packageName = L"Microsoft.CommandPalette"; + std::wstring launchPath = L"shell:AppsFolder\\Microsoft.CommandPalette_8wekyb3d8bbwe!App"; #ifdef IS_DEV_BRANDING - packageName = L"Microsoft.CommandPalette.Dev"; + packageName = L"Microsoft.CommandPalette.Dev"; + launchPath = L"shell:AppsFolder\\Microsoft.CommandPalette.Dev_8wekyb3d8bbwe!App"; #endif - if (!package::GetRegisteredPackage(packageName, false).has_value()) + + if (!package::GetRegisteredPackage(packageName, false).has_value()) + { + try { Logger::info(L"CmdPal not installed. Installing..."); @@ -238,28 +252,34 @@ public: } } } + catch (std::exception& e) + { + std::string errorMessage{ "Exception thrown while trying to install CmdPal package: " }; + errorMessage += e.what(); + Logger::error(errorMessage); + } } - catch (std::exception& e) + + if (!package::GetRegisteredPackage(packageName, false).has_value()) { - std::string errorMessage{ "Exception thrown while trying to install CmdPal package: " }; - errorMessage += e.what(); - Logger::error(errorMessage); + Logger::error("Cmdpal is not registered, quit.."); + return; } - try + + if (!firstEnableCall) { -#ifdef IS_DEV_BRANDING - LaunchApp(std::wstring{ L"shell:AppsFolder\\" } + L"Microsoft.CommandPalette.Dev_8wekyb3d8bbwe!App", L"RunFromPT", false); -#else - LaunchApp(std::wstring{ L"shell:AppsFolder\\" } + L"Microsoft.CommandPalette_8wekyb3d8bbwe!App", L"RunFromPT", false); -#endif + Logger::trace("Not first attempt, try to launch"); + LaunchApp(launchPath, L"RunFromPT", false /*no elevated*/, false /*error pop up*/); } - catch (std::exception& e) + else { - std::string errorMessage{ "Exception thrown while trying to launch CmdPal: " }; - errorMessage += e.what(); - Logger::error(errorMessage); - throw; + // If first time enable, do retry launch. + Logger::trace("First attempt, try to launch"); + std::thread launchThread(&CmdPal::RetryLaunch, launchPath); + launchThread.detach(); } + + firstEnableCall = false; } virtual void disable() @@ -267,7 +287,44 @@ public: Logger::trace("CmdPal::disable()"); TerminateCmdPal(); - m_enabled = false; + CmdPal::m_enabled.store(false); + } + + static void RetryLaunch(std::wstring path) + { + const int base_delay_milliseconds = 1000; + int max_retry = 9; // 2**9 - 1 seconds. Control total wait time within 10 min. + int retry = 0; + do + { + auto launch_result = LaunchApp(path, L"RunFromPT", false, retry < max_retry); + if (launch_result) + { + Logger::info(L"CmdPal launched successfully after {} retries.", retry); + return; + } + else + { + Logger::error(L"Retry {} launch CmdPal launch failed.", retry); + } + + // When we got max retry, we don't need to wait for the next retry. + if (retry < max_retry) + { + int delay = base_delay_milliseconds * (1 << (retry)); + std::this_thread::sleep_for(std::chrono::milliseconds(delay)); + } + ++retry; + } while (retry <= max_retry && m_enabled.load() && !m_launched.load()); + + if (!m_enabled.load() || m_launched.load()) + { + Logger::error(L"Retry cancelled. CmdPal is disabled or already launched."); + } + else + { + Logger::error(L"CmdPal launch failed after {} attempts.", retry); + } } virtual bool on_hotkey(size_t) override @@ -282,11 +339,14 @@ public: virtual bool is_enabled() override { - return m_enabled; + return CmdPal::m_enabled.load(); } }; +std::atomic CmdPal::m_enabled{ false }; +std::atomic CmdPal::m_launched{ false }; + extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() { return new CmdPal(); -} +} \ No newline at end of file diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppStateModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppStateModel.cs index 69d38a8655..649e49fbc7 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppStateModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/AppStateModel.cs @@ -49,7 +49,7 @@ public partial class AppStateModel : ObservableObject // Read the JSON content from the file var jsonContent = File.ReadAllText(FilePath); - var loaded = JsonSerializer.Deserialize(jsonContent, _deserializerOptions); + var loaded = JsonSerializer.Deserialize(jsonContent, JsonSerializationContext.Default.AppStateModel); Debug.WriteLine(loaded != null ? "Loaded settings file" : "Failed to parse"); @@ -73,7 +73,7 @@ public partial class AppStateModel : ObservableObject try { // Serialize the main dictionary to JSON and save it to the file - var settingsJson = JsonSerializer.Serialize(model, _serializerOptions); + var settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.AppStateModel); // Is it valid JSON? if (JsonNode.Parse(settingsJson) is JsonObject newSettings) @@ -89,7 +89,7 @@ public partial class AppStateModel : ObservableObject savedSettings[item.Key] = item.Value != null ? item.Value.DeepClone() : null; } - var serialized = savedSettings.ToJsonString(_serializerOptions); + var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel.Options); File.WriteAllText(FilePath, serialized); // TODO: Instead of just raising the event here, we should @@ -122,18 +122,19 @@ public partial class AppStateModel : ObservableObject return Path.Combine(directory, "state.json"); } - private static readonly JsonSerializerOptions _serializerOptions = new() - { - WriteIndented = true, - Converters = { new JsonStringEnumConverter() }, - }; + // [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] + // private static readonly JsonSerializerOptions _serializerOptions = new() + // { + // WriteIndented = true, + // Converters = { new JsonStringEnumConverter() }, + // }; - private static readonly JsonSerializerOptions _deserializerOptions = new() - { - PropertyNameCaseInsensitive = true, - IncludeFields = true, - AllowTrailingCommas = true, - PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate, - ReadCommentHandling = JsonCommentHandling.Skip, - }; + // private static readonly JsonSerializerOptions _deserializerOptions = new() + // { + // PropertyNameCaseInsensitive = true, + // IncludeFields = true, + // AllowTrailingCommas = true, + // PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate, + // ReadCommentHandling = JsonCommentHandling.Skip, + // }; } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/CreatedExtensionForm.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/CreatedExtensionForm.cs index 1939162662..44bcb49cb3 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/CreatedExtensionForm.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/CreatedExtensionForm.cs @@ -13,12 +13,13 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase { public CreatedExtensionForm(string name, string displayName, string path) { + var serializeString = (string? s) => JsonSerializer.Serialize(s, JsonSerializationContext.Default.String); TemplateJson = CardTemplate; DataJson = $$""" { - "name": {{JsonSerializer.Serialize(name)}}, - "directory": {{JsonSerializer.Serialize(path)}}, - "displayName": {{JsonSerializer.Serialize(displayName)}} + "name": {{serializeString(name)}}, + "directory": {{serializeString(path)}}, + "displayName": {{serializeString(displayName)}} } """; _name = name; @@ -28,13 +29,13 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase public override ICommandResult SubmitForm(string inputs, string data) { - JsonObject? dataInput = JsonNode.Parse(data)?.AsObject(); + var dataInput = JsonNode.Parse(data)?.AsObject(); if (dataInput == null) { return CommandResult.KeepOpen(); } - string verb = dataInput["x"]?.AsValue()?.ToString() ?? string.Empty; + var verb = dataInput["x"]?.AsValue()?.ToString() ?? string.Empty; return verb switch { "sln" => OpenSolution(), @@ -47,7 +48,7 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase private ICommandResult OpenSolution() { string[] parts = [_path, _name, $"{_name}.sln"]; - string pathToSolution = Path.Combine(parts); + var pathToSolution = Path.Combine(parts); ShellHelpers.OpenInShell(pathToSolution); return CommandResult.Hide(); } @@ -55,7 +56,7 @@ internal sealed partial class CreatedExtensionForm : NewExtensionFormBase private ICommandResult OpenDirectory() { string[] parts = [_path, _name]; - string pathToDir = Path.Combine(parts); + var pathToDir = Path.Combine(parts); ShellHelpers.OpenInShell(pathToDir); return CommandResult.Hide(); } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/NewExtensionForm.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/NewExtensionForm.cs index 698faf0335..aca45f3494 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/NewExtensionForm.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Commands/NewExtensionForm.cs @@ -194,9 +194,8 @@ internal sealed partial class NewExtensionForm : NewExtensionFormBase Directory.Delete(tempDir, true); } - private string FormatJsonString(string str) - { + private string FormatJsonString(string str) => + // Escape the string for JSON - return JsonSerializer.Serialize(str); - } + JsonSerializer.Serialize(str, JsonSerializationContext.Default.String); } diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentFormViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentFormViewModel.cs index e1bbe0b604..5af1959cd6 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentFormViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ContentFormViewModel.cs @@ -51,15 +51,16 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference JsonSerializer.Serialize(s, JsonSerializationContext.Default.String); // todo: we could probably stick Card.Errors in there too var dataJson = $$""" { - "error_message": {{JsonSerializer.Serialize(e.Message)}}, - "error_stack": {{JsonSerializer.Serialize(e.StackTrace)}}, - "inner_exception": {{JsonSerializer.Serialize(e.InnerException?.Message)}}, - "template_json": {{JsonSerializer.Serialize(TemplateJson)}}, - "data_json": {{JsonSerializer.Serialize(DataJson)}} + "error_message": {{serializeString(e.Message)}}, + "error_stack": {{serializeString(e.StackTrace)}}, + "inner_exception": {{serializeString(e.InnerException?.Message)}}, + "template_json": {{serializeString(TemplateJson)}}, + "data_json": {{serializeString(DataJson)}} } """; var cardJson = template.Expand(dataJson); diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Microsoft.CmdPal.UI.ViewModels.csproj b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Microsoft.CmdPal.UI.ViewModels.csproj index 342dbe251c..8057bb09be 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Microsoft.CmdPal.UI.ViewModels.csproj +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Microsoft.CmdPal.UI.ViewModels.csproj @@ -1,5 +1,7 @@  + + enable enable @@ -67,4 +69,15 @@ + + + diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionService.cs index 576ea08140..6d59aa66b4 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionService.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionService.cs @@ -11,7 +11,7 @@ using Windows.Foundation.Collections; namespace Microsoft.CmdPal.UI.ViewModels.Models; -public class ExtensionService : IExtensionService, IDisposable +public partial class ExtensionService : IExtensionService, IDisposable { public event TypedEventHandler>? OnExtensionAdded; diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionWrapper.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionWrapper.cs index ed15268507..83644c8d44 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionWrapper.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/Models/ExtensionWrapper.cs @@ -12,6 +12,7 @@ using Windows.Win32; using Windows.Win32.System.Com; using WinRT; +// [assembly: System.Runtime.CompilerServices.DisableRuntimeMarshalling] namespace Microsoft.CmdPal.UI.ViewModels.Models; public class ExtensionWrapper : IExtensionWrapper @@ -113,25 +114,36 @@ public class ExtensionWrapper : IExtensionWrapper // -2147467262: E_NOINTERFACE // -2147024893: E_PATH_NOT_FOUND var guid = typeof(IExtension).GUID; - var hr = PInvoke.CoCreateInstance(Guid.Parse(ExtensionClassId), null, CLSCTX.CLSCTX_LOCAL_SERVER, guid, out var extensionObj); - if (hr.Value == -2147024893) + unsafe { - Logger.LogDebug($"Failed to find {ExtensionDisplayName}: {hr}. It may have been uninstalled or deleted."); + var hr = PInvoke.CoCreateInstance(Guid.Parse(ExtensionClassId), null, CLSCTX.CLSCTX_LOCAL_SERVER, guid, out var extensionObj); - // We don't really need to throw this exception. - // We'll just return out nothing. - return; + if (hr.Value == -2147024893) + { + Logger.LogDebug($"Failed to find {ExtensionDisplayName}: {hr}. It may have been uninstalled or deleted."); + + // We don't really need to throw this exception. + // We'll just return out nothing. + return; + } + + extensionPtr = Marshal.GetIUnknownForObject((nint)extensionObj); + if (hr < 0) + { + Logger.LogDebug($"Failed to instantiate {ExtensionDisplayName}: {hr}"); + Marshal.ThrowExceptionForHR(hr); + } + + // extensionPtr = Marshal.GetIUnknownForObject(extensionObj); + extensionPtr = (nint)extensionObj; + if (hr < 0) + { + Marshal.ThrowExceptionForHR(hr); + } + + _extensionObject = MarshalInterface.FromAbi(extensionPtr); } - - extensionPtr = Marshal.GetIUnknownForObject(extensionObj); - if (hr < 0) - { - Logger.LogDebug($"Failed to instantiate {ExtensionDisplayName}: {hr}"); - Marshal.ThrowExceptionForHR(hr); - } - - _extensionObject = MarshalInterface.FromAbi(extensionPtr); } finally { diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/NativeMethods.json b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/NativeMethods.json new file mode 100644 index 0000000000..59fa7259c4 --- /dev/null +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/NativeMethods.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "allowMarshaling": false +} diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/PageViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/PageViewModel.cs index a082f0acd2..9a1cda196b 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/PageViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/PageViewModel.cs @@ -99,7 +99,7 @@ public partial class PageViewModel : ExtensionObjectViewModel, IPageContext //// Run on background thread from ListPage.xaml.cs [RelayCommand] - private Task InitializeAsync() + internal Task InitializeAsync() { // TODO: We may want a SemaphoreSlim lock here. diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/RecentCommandsManager.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/RecentCommandsManager.cs index 9e971ae510..c740341c7a 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/RecentCommandsManager.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/RecentCommandsManager.cs @@ -10,7 +10,7 @@ namespace Microsoft.CmdPal.UI.ViewModels; public partial class RecentCommandsManager : ObservableObject { [JsonInclude] - private List History { get; set; } = []; + internal List History { get; set; } = []; public RecentCommandsManager() { diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs index 27dd119b48..ae97849f7a 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/SettingsModel.cs @@ -93,7 +93,7 @@ public partial class SettingsModel : ObservableObject // Read the JSON content from the file var jsonContent = File.ReadAllText(FilePath); - var loaded = JsonSerializer.Deserialize(jsonContent, _deserializerOptions); + var loaded = JsonSerializer.Deserialize(jsonContent, JsonSerializationContext.Default.SettingsModel); Debug.WriteLine(loaded != null ? "Loaded settings file" : "Failed to parse"); @@ -117,7 +117,7 @@ public partial class SettingsModel : ObservableObject try { // Serialize the main dictionary to JSON and save it to the file - var settingsJson = JsonSerializer.Serialize(model, _serializerOptions); + var settingsJson = JsonSerializer.Serialize(model, JsonSerializationContext.Default.SettingsModel); // Is it valid JSON? if (JsonNode.Parse(settingsJson) is JsonObject newSettings) @@ -133,7 +133,7 @@ public partial class SettingsModel : ObservableObject savedSettings[item.Key] = item.Value != null ? item.Value.DeepClone() : null; } - var serialized = savedSettings.ToJsonString(_serializerOptions); + var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.Options); File.WriteAllText(FilePath, serialized); // TODO: Instead of just raising the event here, we should @@ -166,19 +166,34 @@ public partial class SettingsModel : ObservableObject return Path.Combine(directory, "settings.json"); } - private static readonly JsonSerializerOptions _serializerOptions = new() - { - WriteIndented = true, - Converters = { new JsonStringEnumConverter() }, - }; + // [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")] + // private static readonly JsonSerializerOptions _serializerOptions = new() + // { + // WriteIndented = true, + // Converters = { new JsonStringEnumConverter() }, + // }; + // private static readonly JsonSerializerOptions _deserializerOptions = new() + // { + // PropertyNameCaseInsensitive = true, + // IncludeFields = true, + // Converters = { new JsonStringEnumConverter() }, + // AllowTrailingCommas = true, + // }; +} - private static readonly JsonSerializerOptions _deserializerOptions = new() - { - PropertyNameCaseInsensitive = true, - IncludeFields = true, - Converters = { new JsonStringEnumConverter() }, - AllowTrailingCommas = true, - }; +[JsonSerializable(typeof(float))] +[JsonSerializable(typeof(int))] +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(bool))] +[JsonSerializable(typeof(HistoryItem))] +[JsonSerializable(typeof(SettingsModel))] +[JsonSerializable(typeof(AppStateModel))] +[JsonSerializable(typeof(List), TypeInfoPropertyName = "HistoryList")] +[JsonSerializable(typeof(Dictionary), TypeInfoPropertyName = "Dictionary")] +[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true, IncludeFields = true, PropertyNameCaseInsensitive = true, AllowTrailingCommas = true)] +[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Just used here")] +internal sealed partial class JsonSerializationContext : JsonSerializerContext +{ } public enum MonitorBehavior diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ShellViewModel.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ShellViewModel.cs index 043598196b..d86831d0a1 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ShellViewModel.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI.ViewModels/ShellViewModel.cs @@ -109,9 +109,12 @@ public partial class ShellViewModel(IServiceProvider _serviceProvider, TaskSched // TODO GH #239 switch back when using the new MD text block // _ = _queue.EnqueueAsync(() => _ = Task.Factory.StartNew( - () => + async () => { - var result = (bool)viewModel.InitializeCommand.ExecutionTask.GetResultOrDefault()!; + // bool f = await viewModel.InitializeCommand.ExecutionTask.; + // var result = viewModel.InitializeCommand.ExecutionTask.GetResultOrDefault()!; + // var result = viewModel.InitializeCommand.ExecutionTask.GetResultOrDefault()!; + var result = await viewModel.InitializeAsync(); CurrentPage = viewModel; // result ? viewModel : null; ////LoadedState = result ? ViewModelLoadedState.Loaded : ViewModelLoadedState.Error; diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/AddBookmarkForm.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/AddBookmarkForm.cs index 9b3f54a21f..5da419cd40 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/AddBookmarkForm.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/AddBookmarkForm.cs @@ -34,7 +34,7 @@ internal sealed partial class AddBookmarkForm : FormContent "style": "text", "id": "name", "label": "{{Resources.bookmarks_form_name_label}}", - "value": {{JsonSerializer.Serialize(name)}}, + "value": {{JsonSerializer.Serialize(name, BookmarkSerializationContext.Default.String)}}, "isRequired": true, "errorMessage": "{{Resources.bookmarks_form_name_required}}" }, @@ -42,7 +42,7 @@ internal sealed partial class AddBookmarkForm : FormContent "type": "Input.Text", "style": "text", "id": "bookmark", - "value": {{JsonSerializer.Serialize(url)}}, + "value": {{JsonSerializer.Serialize(url, BookmarkSerializationContext.Default.String)}}, "label": "{{Resources.bookmarks_form_bookmark_label}}", "isRequired": true, "errorMessage": "{{Resources.bookmarks_form_bookmark_required}}" diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/BookmarkSerializationContext.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/BookmarkSerializationContext.cs new file mode 100644 index 0000000000..9730bf214d --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/BookmarkSerializationContext.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace Microsoft.CmdPal.Ext.Bookmarks; + +[JsonSerializable(typeof(float))] +[JsonSerializable(typeof(int))] +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(bool))] +[JsonSerializable(typeof(BookmarkData))] +[JsonSerializable(typeof(Bookmarks))] +[JsonSerializable(typeof(List), TypeInfoPropertyName = "BookmarkList")] +[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true, IncludeFields = true, PropertyNameCaseInsensitive = true, AllowTrailingCommas = true)] +internal sealed partial class BookmarkSerializationContext : JsonSerializerContext +{ +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/Bookmarks.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/Bookmarks.cs index 7c3a1dd1e0..8f2e257782 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/Bookmarks.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/Bookmarks.cs @@ -28,7 +28,7 @@ public sealed class Bookmarks if (!string.IsNullOrEmpty(jsonStringReading)) { - data = JsonSerializer.Deserialize(jsonStringReading, _jsonOptions) ?? new Bookmarks(); + data = JsonSerializer.Deserialize(jsonStringReading, BookmarkSerializationContext.Default.Bookmarks) ?? new Bookmarks(); } } @@ -37,7 +37,7 @@ public sealed class Bookmarks public static void WriteToFile(string path, Bookmarks data) { - var jsonString = JsonSerializer.Serialize(data, _jsonOptions); + var jsonString = JsonSerializer.Serialize(data, BookmarkSerializationContext.Default.Bookmarks); File.WriteAllText(BookmarksCommandProvider.StateJsonPath(), jsonString); } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/Microsoft.CmdPal.Ext.Bookmarks.csproj b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/Microsoft.CmdPal.Ext.Bookmarks.csproj index 7bbf1efc5f..40c3cca9f2 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/Microsoft.CmdPal.Ext.Bookmarks.csproj +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Bookmark/Microsoft.CmdPal.Ext.Bookmarks.csproj @@ -1,5 +1,6 @@  + Microsoft.CmdPal.Ext.Bookmarks enable @@ -16,7 +17,7 @@ - + Resources.resx @@ -24,7 +25,7 @@ True - + PreserveNewest @@ -39,5 +40,5 @@ PublicResXFileCodeGenerator - + diff --git a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSerializationContext.cs b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSerializationContext.cs index 6fbc734560..6d92cdc146 100644 --- a/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSerializationContext.cs +++ b/src/modules/cmdpal/extensionsdk/Microsoft.CommandPalette.Extensions.Toolkit/JsonSerializationContext.cs @@ -16,7 +16,6 @@ namespace Microsoft.CommandPalette.Extensions.Toolkit; [JsonSerializable(typeof(List))] [JsonSerializable(typeof(Dictionary), TypeInfoPropertyName = "Dictionary")] [JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true)] -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Just used here")] internal partial class JsonSerializationContext : JsonSerializerContext { }