diff --git a/src/app/dev/DevToys.Blazor/BuiltInTools/ExtensionsManager/ExtensionsManagerGuiTool.cs b/src/app/dev/DevToys.Blazor/BuiltInTools/ExtensionsManager/ExtensionsManagerGuiTool.cs index 4d81c47424..aa32cb9826 100644 --- a/src/app/dev/DevToys.Blazor/BuiltInTools/ExtensionsManager/ExtensionsManagerGuiTool.cs +++ b/src/app/dev/DevToys.Blazor/BuiltInTools/ExtensionsManager/ExtensionsManagerGuiTool.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using DevToys.Blazor.BuiltInTools.Settings; +using DevToys.Core; using NuGet.Packaging; using NuGet.Packaging.Core; @@ -131,13 +132,13 @@ public void OnDataReceived(string dataTypeName, object? parsedData) private void OnFindMoreExtensionsOnlineButtonClick() { // TODO: Open documentation online - Shell.OpenFileInShell("http://url"); + OSHelper.OpenFileInShell("http://url"); } private void OnLearnDevelopExtensionButtonClick() { // TODO: Open documentation online - Shell.OpenFileInShell("http://url"); + OSHelper.OpenFileInShell("http://url"); } private async ValueTask OnInstallExtensionButtonClickAsync() @@ -275,7 +276,7 @@ IUIButton uninstallButton static void NavigateToUrl(string url) { - Shell.OpenFileInShell(url); + OSHelper.OpenFileInShell(url); } } @@ -318,7 +319,7 @@ await _view.Value.OpenDialogAsync( void OnTermsConditionsDialogButtonClick() { // TODO: Open the terms and conditions page. - Shell.OpenFileInShell("https://url"); + OSHelper.OpenFileInShell("https://url"); } void OnIUnderstandDialogButtonClick() diff --git a/src/app/dev/DevToys.Blazor/BuiltInTools/Settings/SettingsGuiTool.cs b/src/app/dev/DevToys.Blazor/BuiltInTools/Settings/SettingsGuiTool.cs index ae015a9aa9..1b11bf33f0 100644 --- a/src/app/dev/DevToys.Blazor/BuiltInTools/Settings/SettingsGuiTool.cs +++ b/src/app/dev/DevToys.Blazor/BuiltInTools/Settings/SettingsGuiTool.cs @@ -1,5 +1,6 @@ using System.Reflection; using DevToys.Blazor.Core.Languages; +using DevToys.Core; using DevToys.Core.Settings; namespace DevToys.Blazor.BuiltInTools.Settings; @@ -224,7 +225,7 @@ public void OnDataReceived(string dataTypeName, object? parsedData) private void OnHelpTranslatingButtonClick() { - Shell.OpenFileInShell("https://crowdin.com/project/devtoys"); + OSHelper.OpenFileInShell("https://crowdin.com/project/devtoys"); } private void OnLanguageSelected(IUIDropDownListItem? selectedItem) diff --git a/src/app/dev/DevToys.Blazor/Components/Collections/ListBox/ListBoxItem.razor b/src/app/dev/DevToys.Blazor/Components/Collections/ListBox/ListBoxItem.razor index c48731d2d6..2ed29e4658 100644 --- a/src/app/dev/DevToys.Blazor/Components/Collections/ListBox/ListBoxItem.razor +++ b/src/app/dev/DevToys.Blazor/Components/Collections/ListBox/ListBoxItem.razor @@ -2,12 +2,16 @@ @inherits StyledComponentBase -
  • - @ChildContent -
  • + +
  • + @ChildContent +
  • +
    \ No newline at end of file diff --git a/src/app/dev/DevToys.Blazor/Components/Collections/ListBox/ListBoxItem.razor.cs b/src/app/dev/DevToys.Blazor/Components/Collections/ListBox/ListBoxItem.razor.cs index 07b0b43027..27de721925 100644 --- a/src/app/dev/DevToys.Blazor/Components/Collections/ListBox/ListBoxItem.razor.cs +++ b/src/app/dev/DevToys.Blazor/Components/Collections/ListBox/ListBoxItem.razor.cs @@ -1,7 +1,11 @@ -namespace DevToys.Blazor.Components; +using System.Collections.ObjectModel; + +namespace DevToys.Blazor.Components; public partial class ListBoxItem : StyledComponentBase { + private readonly ICollection _contextMenuItems = new ObservableCollection(); + [Parameter] public object Item { get; set; } = default!; @@ -17,6 +21,9 @@ public partial class ListBoxItem : StyledComponentBase [Parameter] public RenderFragment? ChildContent { get; set; } + [Parameter] + public EventCallback OnBuildingContextMenu { get; set; } + protected override void OnParametersSet() { if (IsSelected) @@ -44,4 +51,9 @@ private Task OnClickAsync() { return OnSelected.InvokeAsync(Item); } + + private Task OnContextMenuOpeningAsync() + { + return OnBuildingContextMenu.InvokeAsync(new ListBoxItemBuildingContextMenuEventArgs(Item, _contextMenuItems)); + } } diff --git a/src/app/dev/DevToys.Blazor/Components/Collections/ListBox/ListBoxItemBuildingContextMenuEventArgs.cs b/src/app/dev/DevToys.Blazor/Components/Collections/ListBox/ListBoxItemBuildingContextMenuEventArgs.cs new file mode 100644 index 0000000000..99318319df --- /dev/null +++ b/src/app/dev/DevToys.Blazor/Components/Collections/ListBox/ListBoxItemBuildingContextMenuEventArgs.cs @@ -0,0 +1,15 @@ +namespace DevToys.Blazor.Components; + +public sealed class ListBoxItemBuildingContextMenuEventArgs : EventArgs +{ + internal ListBoxItemBuildingContextMenuEventArgs(object? item, ICollection items) + { + Guard.IsNotNull(items); + ItemValue = item; + ContextMenuItems = items; + } + + internal object? ItemValue { get; } + + internal ICollection ContextMenuItems { get; } +} diff --git a/src/app/dev/DevToys.Blazor/Components/Menu/ContextMenu/ContextMenu.razor.cs b/src/app/dev/DevToys.Blazor/Components/Menu/ContextMenu/ContextMenu.razor.cs index 0553f211cd..ce689ef4d2 100644 --- a/src/app/dev/DevToys.Blazor/Components/Menu/ContextMenu/ContextMenu.razor.cs +++ b/src/app/dev/DevToys.Blazor/Components/Menu/ContextMenu/ContextMenu.razor.cs @@ -66,7 +66,10 @@ private async Task OpenMenuAsync(MouseEventArgs ev) if (IsActuallyEnabled) { // This may update the item list. - await OnContextMenuOpening.InvokeAsync(); + if (OnContextMenuOpening.HasDelegate) + { + await OnContextMenuOpening.InvokeAsync(); + } if (Items?.Count == 0) { @@ -83,7 +86,10 @@ private async Task OpenMenuAsync(MouseEventArgs ev) _isOpen = true; StateHasChanged(); - await OnContextMenuOpened.InvokeAsync(); + if (OnContextMenuOpened.HasDelegate) + { + await OnContextMenuOpened.InvokeAsync(); + } } } } @@ -95,7 +101,10 @@ private async Task CloseMenuAsync(MouseEventArgs? _) _isOpen = false; StateHasChanged(); - await OnContextMenuClosed.InvokeAsync(); + if (OnContextMenuClosed.HasDelegate) + { + await OnContextMenuClosed.InvokeAsync(); + } } private async Task OnEscapeKeyPressedAsync() @@ -119,7 +128,10 @@ private void OnContextMenuItemSelected(int itemIndex) if (_listBox is not null && _listBox.SelectedItem is not null && _listBox.SelectedItem.IsEnabled) { CloseMenuAsync(null).Forget(); - _listBox.SelectedItem.OnClick.InvokeAsync(_listBox.SelectedItem).Forget(); + if (_listBox.SelectedItem.OnClick.HasDelegate) + { + _listBox.SelectedItem.OnClick.InvokeAsync(_listBox.SelectedItem).Forget(); + } } } } diff --git a/src/app/dev/DevToys.Blazor/Components/Menu/NavBar/NavBar.razor b/src/app/dev/DevToys.Blazor/Components/Menu/NavBar/NavBar.razor index 286a95653e..8f7d67297c 100644 --- a/src/app/dev/DevToys.Blazor/Components/Menu/NavBar/NavBar.razor +++ b/src/app/dev/DevToys.Blazor/Components/Menu/NavBar/NavBar.razor @@ -131,7 +131,8 @@ TitleTemplate="NavBarItemTitleTemplate" IconTemplate="NavBarItemIconTemplate" Children="item is IGroup itemGroup ? itemGroup.ChildrenItems : null" - OnSelected="OnItemSelectedAsync" /> + OnSelected="OnItemSelectedAsync" + OnBuildingContextMenu="OnBuildingContextMenuAsync" /> } } @@ -153,7 +154,8 @@ TitleTemplate="NavBarItemTitleTemplate" IconTemplate="NavBarItemIconTemplate" Children="item is IGroup itemGroup ? itemGroup.ChildrenItems : null" - OnSelected="OnItemSelectedAsync" /> + OnSelected="OnItemSelectedAsync" + OnBuildingContextMenu="OnBuildingContextMenuAsync" /> } } diff --git a/src/app/dev/DevToys.Blazor/Components/Menu/NavBar/NavBar.razor.cs b/src/app/dev/DevToys.Blazor/Components/Menu/NavBar/NavBar.razor.cs index 792eb8e6e2..bcbec403de 100644 --- a/src/app/dev/DevToys.Blazor/Components/Menu/NavBar/NavBar.razor.cs +++ b/src/app/dev/DevToys.Blazor/Components/Menu/NavBar/NavBar.razor.cs @@ -46,6 +46,9 @@ public partial class NavBar [Parameter] public EventCallback SelectedItemChanged { get; set; } + [Parameter] + public EventCallback OnBuildingContextMenu { get; set; } + [Parameter] public bool CanGoBack { get; set; } @@ -204,6 +207,12 @@ private Task OnItemSelectedAsync(TElement item) return SelectedItemChanged.InvokeAsync(item); } + private Task OnBuildingContextMenuAsync(ListBoxItemBuildingContextMenuEventArgs args) + { + Guard.IsAssignableToType(args.ItemValue); + return OnBuildingContextMenu.InvokeAsync(args); + } + private void SidebarState_IsHiddenChanged(object? sender, EventArgs e) { if (OnHiddenStateChanged.HasDelegate) diff --git a/src/app/dev/DevToys.Blazor/Components/Menu/NavBar/NavBarItem.razor b/src/app/dev/DevToys.Blazor/Components/Menu/NavBar/NavBarItem.razor index 7b6ccb12cb..ccc6ce121e 100644 --- a/src/app/dev/DevToys.Blazor/Components/Menu/NavBar/NavBarItem.razor +++ b/src/app/dev/DevToys.Blazor/Components/Menu/NavBar/NavBarItem.razor @@ -20,6 +20,7 @@ IsSelected="Item == OwnerNavBar.SelectedItem" Class="@FinalCssClasses" Style="margin: 3px 5px;" + OnBuildingContextMenu=@OnBuildingContextMenuAsync OnSelected=@OnItemSelectedAsync>
    - diff --git a/src/app/dev/DevToys.Blazor/Pages/SubPages/ToolGroup.razor.cs b/src/app/dev/DevToys.Blazor/Pages/SubPages/ToolGroup.razor.cs index 04afa7ca6b..1382d2c5b1 100644 --- a/src/app/dev/DevToys.Blazor/Pages/SubPages/ToolGroup.razor.cs +++ b/src/app/dev/DevToys.Blazor/Pages/SubPages/ToolGroup.razor.cs @@ -3,6 +3,8 @@ using DevToys.Core.Tools.ViewItems; using DevToys.Blazor.Components; using System.Reflection; +using DevToys.Business.Services; +using DevToys.Core; namespace DevToys.Blazor.Pages.SubPages; @@ -27,6 +29,9 @@ var assemblyInformationalVersion [Import] internal ToolGroupPageViewModel ViewModel { get; set; } = default!; + [Import] + internal CommandLineLauncherService CommandLineLauncherService { get; set; } = default!; + [Parameter] public GroupViewItem? GroupViewItem { get; set; } @@ -62,11 +67,11 @@ private void OnToolSelected(object item) private void OnOpenInNewWindow(GuiToolInstance item) { - // TODO + CommandLineLauncherService.LaunchTool(item.InternalComponentName); } private void OnSuggestToolIdeaClick() { - Shell.OpenFileInShell("https://github.com/veler/DevToys/issues/new/choose"); + OSHelper.OpenFileInShell("https://github.com/veler/DevToys/issues/new/choose"); } } diff --git a/src/app/dev/DevToys.Business/Services/CommandLineLauncherService.cs b/src/app/dev/DevToys.Business/Services/CommandLineLauncherService.cs new file mode 100644 index 0000000000..3f6f1ab770 --- /dev/null +++ b/src/app/dev/DevToys.Business/Services/CommandLineLauncherService.cs @@ -0,0 +1,50 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Messaging; +using DevToys.Api; +using DevToys.Business.ViewModels; +using DevToys.Core; +using DevToys.Core.Models; +using DevToys.Core.Tools; + +namespace DevToys.Business.Services; + +[Export] +internal sealed class CommandLineLauncherService : ObservableRecipient +{ + internal const string ToolArgument = "--tool:"; + + private readonly GuiToolProvider _guiToolProvider; + private readonly IMefProvider _mefProvider; + + [ImportingConstructor] + public CommandLineLauncherService(GuiToolProvider guiToolProvider, IMefProvider mefProvider) + { + _mefProvider = mefProvider; + _guiToolProvider = guiToolProvider; + } + + internal void HandleCommandLineArguments() + { + if (Environment.CommandLine.Contains(ToolArgument)) + { + string toolName = Environment.CommandLine.Substring(Environment.CommandLine.IndexOf(ToolArgument) + ToolArgument.Length); + GuiToolInstance? tool = _guiToolProvider.GetToolFromInternalName(toolName); + if (tool is not null) + { + // Make sure MainWindowViewModel is initialized. + _mefProvider.Import(); + Messenger.Send(new ChangeSelectedMenuItemMessage(tool)); + } + } + } + + internal void LaunchTool(string toolInternalName) + { + GuiToolInstance? tool = _guiToolProvider.GetToolFromInternalName(toolInternalName); + if (tool is not null) + { + string appStartExe = Process.GetCurrentProcess().MainModule!.FileName; + OSHelper.OpenFileInShell(appStartExe, $"{ToolArgument}{toolInternalName}"); + } + } +} diff --git a/src/app/dev/DevToys.Core/OSHelper.cs b/src/app/dev/DevToys.Core/OSHelper.cs index 79a066739c..2b63d4d566 100644 --- a/src/app/dev/DevToys.Core/OSHelper.cs +++ b/src/app/dev/DevToys.Core/OSHelper.cs @@ -37,4 +37,37 @@ public static bool IsOsSupported(IReadOnlyList targetPlatforms) return true; } + + public static void OpenFileInShell(string fileOrUrl, string? arguments = null) + { + Guard.IsNotNullOrWhiteSpace(fileOrUrl); + + try + { + var startInfo = new ProcessStartInfo(fileOrUrl, arguments!); + startInfo.UseShellExecute = true; + Process.Start(startInfo); + } + catch + { + // hack because of this: https://github.com/dotnet/corefx/issues/10361 + if (OperatingSystem.IsWindows()) + { + fileOrUrl = fileOrUrl.Replace("&", "^&"); + Process.Start(new ProcessStartInfo(fileOrUrl, arguments!) { UseShellExecute = true }); + } + else if (OperatingSystem.IsLinux() || OperatingSystem.IsFreeBSD()) + { + Process.Start("xdg-open", new[] { fileOrUrl, arguments! }); + } + else if (OperatingSystem.IsMacOS() || OperatingSystem.IsMacCatalyst()) + { + Process.Start("open", new[] { fileOrUrl, arguments! }); + } + else + { + throw; + } + } + } } diff --git a/src/app/dev/DevToys.Localization/Strings/ToolGroupPage/ToolGroupPage.Designer.cs b/src/app/dev/DevToys.Localization/Strings/ToolGroupPage/ToolGroupPage.Designer.cs index c8b118e4df..a0c7b191a1 100644 --- a/src/app/dev/DevToys.Localization/Strings/ToolGroupPage/ToolGroupPage.Designer.cs +++ b/src/app/dev/DevToys.Localization/Strings/ToolGroupPage/ToolGroupPage.Designer.cs @@ -87,6 +87,15 @@ public static string NotFindingToolSuggestIdea { } } + /// + /// Looks up a localized string similar to Open in a new window. + /// + public static string OpenInNewWindow { + get { + return ResourceManager.GetString("OpenInNewWindow", resourceCulture); + } + } + /// /// Looks up a localized string similar to Welcome to. /// diff --git a/src/app/dev/DevToys.Localization/Strings/ToolGroupPage/ToolGroupPage.resx b/src/app/dev/DevToys.Localization/Strings/ToolGroupPage/ToolGroupPage.resx index 59468f1e1c..734522aa93 100644 --- a/src/app/dev/DevToys.Localization/Strings/ToolGroupPage/ToolGroupPage.resx +++ b/src/app/dev/DevToys.Localization/Strings/ToolGroupPage/ToolGroupPage.resx @@ -126,6 +126,9 @@ Suggest an idea + + Open in a new window + Welcome to diff --git a/src/app/dev/platforms/desktop/DevToys.Linux/Components/BlazorWebView/BlazorWebView.cs b/src/app/dev/platforms/desktop/DevToys.Linux/Components/BlazorWebView/BlazorWebView.cs index 20a76b6c60..5cc34d28bb 100644 --- a/src/app/dev/platforms/desktop/DevToys.Linux/Components/BlazorWebView/BlazorWebView.cs +++ b/src/app/dev/platforms/desktop/DevToys.Linux/Components/BlazorWebView/BlazorWebView.cs @@ -27,6 +27,14 @@ internal BlazorWebView(IServiceProvider serviceProvider) serviceProvider, options, contentRootDir, - hostPageRelativePath); + hostPageRelativePath, + OnBlazorInitialized); + } + + internal event EventHandler? BlazorWebViewInitialized; + + private void OnBlazorInitialized() + { + BlazorWebViewInitialized?.Invoke(this, EventArgs.Empty); } } diff --git a/src/app/dev/platforms/desktop/DevToys.Linux/Components/BlazorWebView/BlazorWebViewBridge.cs b/src/app/dev/platforms/desktop/DevToys.Linux/Components/BlazorWebView/BlazorWebViewBridge.cs index 73b95aced2..55e7c051f4 100644 --- a/src/app/dev/platforms/desktop/DevToys.Linux/Components/BlazorWebView/BlazorWebViewBridge.cs +++ b/src/app/dev/platforms/desktop/DevToys.Linux/Components/BlazorWebView/BlazorWebViewBridge.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Components.WebView; using Microsoft.Extensions.FileProviders; using WebKit; +using Action = System.Action; namespace DevToys.Linux.Components; @@ -22,7 +23,8 @@ public BlazorWebViewBridge( IServiceProvider serviceProvider, BlazorWebViewOptions options, string contentRootRelativeToAppRoot, - string hostPageRelativePath) + string hostPageRelativePath, + Action onBlazorInitialized) : base( serviceProvider, Dispatcher.CreateDefault(), @@ -61,11 +63,11 @@ public BlazorWebViewBridge( source: """ window.__receiveMessageCallbacks = []; - + window.__dispatchMessageCallback = function(message) { window.__receiveMessageCallbacks.forEach(function(callback) { callback(message); }); }; - + window.external = { sendMessage: function(message) { window.webkit.messageHandlers.webview.postMessage(message); @@ -91,6 +93,12 @@ public BlazorWebViewBridge( } Navigate("/"); + + System.Threading.Tasks.Task.Run(async () => + { + await TaskSchedulerAwaiter.SwitchOffMainThreadAsync(CancellationToken.None); + onBlazorInitialized.Invoke(); + }); } protected override void NavigateCore(Uri absoluteUri) @@ -121,12 +129,12 @@ private void HandleUriScheme(URISchemeRequest request) bool allowFallbackOnHostPage = IsUriBaseOfPage(AppOriginUri, uri); if (TryGetResponseContent( - uri, - allowFallbackOnHostPage, - out int statusCode, - out string? statusMessage, - out Stream? content, - out IDictionary? headers)) + uri, + allowFallbackOnHostPage, + out int statusCode, + out string? statusMessage, + out Stream? content, + out IDictionary? headers)) { using var ms = new MemoryStream(); content.CopyTo(ms); diff --git a/src/app/dev/platforms/desktop/DevToys.Linux/MainWindow.cs b/src/app/dev/platforms/desktop/DevToys.Linux/MainWindow.cs index 06ea9a3841..017ec90cd1 100644 --- a/src/app/dev/platforms/desktop/DevToys.Linux/MainWindow.cs +++ b/src/app/dev/platforms/desktop/DevToys.Linux/MainWindow.cs @@ -1,10 +1,13 @@ using DevToys.Api; using DevToys.Blazor.Core.Services; +using DevToys.Business.Services; using DevToys.Core; using DevToys.Linux.Components; using DevToys.Linux.Core; +using Gtk; using Microsoft.Extensions.DependencyInjection; using WebKit; +using Settings = WebKit.Settings; namespace DevToys.Linux; @@ -22,6 +25,9 @@ internal class MainWindow [Import] private TitleBarInfoProvider _titleBarInfoProvider = default!; + + [Import] + private CommandLineLauncherService _commandLineLauncherService = default!; #pragma warning restore IDE0044 // Add readonly modifier private readonly BlazorWebView _blazorGtkWebView; @@ -35,7 +41,8 @@ internal MainWindow(IServiceProvider serviceProvider, Adw.Application applicatio _titleBarInfoProvider.PropertyChanged += TitleBarInfoProvider_PropertyChanged; _blazorGtkWebView = new BlazorWebView(serviceProvider); - _blazorGtkWebView.OnContextMenu += BlazorGtkWebViewOnOnContextMenu; + _blazorGtkWebView.OnContextMenu += BlazorGtkWebViewOnContextMenu; + _blazorGtkWebView.BlazorWebViewInitialized += OnBlazorWebViewInitialized; // Make web view transparent _blazorGtkWebView.SetBackgroundColor(new Gdk.RGBA(Gdk.Internal.RGBAManagedHandle.Create(new Gdk.Internal.RGBAData { Red = 0, Blue = 0, Green = 0, Alpha = 0 }))); @@ -64,7 +71,7 @@ internal MainWindow(IServiceProvider serviceProvider, Adw.Application applicatio _window.Show(); } - private bool BlazorGtkWebViewOnOnContextMenu(WebView sender, WebView.ContextMenuSignalArgs args) + private bool BlazorGtkWebViewOnContextMenu(WebView sender, WebView.ContextMenuSignalArgs args) { // Returning true to prevent the context menu from opening. #if DEBUG @@ -74,6 +81,11 @@ private bool BlazorGtkWebViewOnOnContextMenu(WebView sender, WebView.ContextMenu #endif } + private void OnBlazorWebViewInitialized(object? sender, EventArgs args) + { + InitializeLowPriorityServices(); + } + private void TitleBarInfoProvider_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(TitleBarInfoProvider.Title)) @@ -81,4 +93,10 @@ private void TitleBarInfoProvider_PropertyChanged(object? sender, PropertyChange _window.Title = _titleBarInfoProvider.Title ?? string.Empty; } } + + private void InitializeLowPriorityServices() + { + // Treat command line arguments. + _commandLineLauncherService.HandleCommandLineArguments(); + } } diff --git a/src/app/dev/platforms/desktop/DevToys.MacOS/Controls/BlazorWebView/BlazorWKWebView.cs b/src/app/dev/platforms/desktop/DevToys.MacOS/Controls/BlazorWebView/BlazorWKWebView.cs index af913bdd81..a591ee29f5 100644 --- a/src/app/dev/platforms/desktop/DevToys.MacOS/Controls/BlazorWebView/BlazorWKWebView.cs +++ b/src/app/dev/platforms/desktop/DevToys.MacOS/Controls/BlazorWebView/BlazorWKWebView.cs @@ -92,6 +92,8 @@ internal string? HostPage /// internal event EventHandler? UrlLoading; + internal event EventHandler? BlazorWebViewInitialized; + public void Dispose() { if (_webViewManager is not null) @@ -247,6 +249,12 @@ private void StartWebViewCoreIfPossible() LogStartingInitialNavigation(StartPath); _webViewManager.Navigate(StartPath); + + Task.Run(async () => + { + await TaskSchedulerAwaiter.SwitchOffMainThreadAsync(CancellationToken.None); + BlazorWebViewInitialized?.Invoke(this, EventArgs.Empty); + }); } private static IFileProvider CreateFileProvider(string contentRootDir) diff --git a/src/app/dev/platforms/desktop/DevToys.MacOS/Views/MainWindow.cs b/src/app/dev/platforms/desktop/DevToys.MacOS/Views/MainWindow.cs index e019e75208..759688edf1 100644 --- a/src/app/dev/platforms/desktop/DevToys.MacOS/Views/MainWindow.cs +++ b/src/app/dev/platforms/desktop/DevToys.MacOS/Views/MainWindow.cs @@ -1,6 +1,7 @@ using DevToys.Api; using DevToys.Blazor; using DevToys.Blazor.Core.Services; +using DevToys.Business.Services; using DevToys.Core; using DevToys.Core.Tools; using DevToys.MacOS.Controls.BlazorWebView; @@ -22,6 +23,7 @@ internal sealed class MainWindow : NSWindow private readonly TitleBarInfoProvider _titleBarInfoProvider; private readonly ISettingsProvider _settingsProvider; + private readonly CommandLineLauncherService _commandLineLauncherService; private bool _isInitialized; internal static MainWindow Instance { get; } = new(); @@ -36,6 +38,7 @@ private MainWindow() { Guard.IsNotNull(AppDelegate.MefComposer); _settingsProvider = AppDelegate.MefComposer.Provider.Import(); + _commandLineLauncherService = AppDelegate.MefComposer.Provider.Import(); _titleBarInfoProvider = AppDelegate.MefComposer.Provider.Import(); _titleBarInfoProvider.PropertyChanged += TitleBarInfoProvider_PropertyChanged; @@ -71,6 +74,7 @@ internal async Task ShowAsync() // Make this window the main window of the app. MakeMainWindow(); + OrderFront(null); } private void InitializeView() @@ -101,6 +105,7 @@ private void InitializeView() // Create WebView and add it to NSVisualEffectView Guard.IsNotNull(AppDelegate.ServiceProvider); var webView = new BlazorWkWebView(AppDelegate.ServiceProvider, EnableDeveloperTools); + webView.BlazorWebViewInitialized+= OnBlazorWebViewInitialized; visualEffectView.AddSubview(webView.View); // Make the WKWebView resizing with the window and anchor it below the title bar, so we can still move the window with the title bar. @@ -172,6 +177,11 @@ private void SavePositionAndSize() } } + private void OnBlazorWebViewInitialized(object? sender, EventArgs e) + { + InitializeLowPriorityServices(); + } + private void TitleBarInfoProvider_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(TitleBarInfoProvider.Title)) @@ -179,4 +189,10 @@ private void TitleBarInfoProvider_PropertyChanged(object? sender, PropertyChange Title = _titleBarInfoProvider.Title ?? string.Empty; } } + + private void InitializeLowPriorityServices() + { + // Treat command line arguments. + _commandLineLauncherService.HandleCommandLineArguments(); + } } diff --git a/src/app/dev/platforms/desktop/DevToys.Windows/Core/TaskbarJumpListService.cs b/src/app/dev/platforms/desktop/DevToys.Windows/Core/TaskbarJumpListService.cs index 874232fd27..4d8af14c1b 100644 --- a/src/app/dev/platforms/desktop/DevToys.Windows/Core/TaskbarJumpListService.cs +++ b/src/app/dev/platforms/desktop/DevToys.Windows/Core/TaskbarJumpListService.cs @@ -1,9 +1,7 @@ using System.Windows.Shell; using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Messaging; using DevToys.Api; -using DevToys.Business.ViewModels; -using DevToys.Core.Models; +using DevToys.Business.Services; using DevToys.Core.Tools; using DevToys.Windows.Helpers; using DevToys.Windows.Strings.TaskBar; @@ -13,21 +11,15 @@ namespace DevToys.Windows.Core; [Export(typeof(TaskbarJumpListService))] internal class TaskbarJumpListService : ObservableRecipient { - private const string ToolArgument = "--tool:"; - private readonly GuiToolProvider _guiToolProvider; - private readonly IMefProvider _mefProvider; [ImportingConstructor] - public TaskbarJumpListService(GuiToolProvider guiToolProvider, IMefProvider mefProvider) + public TaskbarJumpListService(GuiToolProvider guiToolProvider) { - _mefProvider = mefProvider; _guiToolProvider = guiToolProvider; _guiToolProvider.FavoriteToolsChanged += GuiToolProvider_FavoriteToolsChanged; RefreshJumpListAsync().Forget(); - - HandleOpenWithJumpList(); } private void GuiToolProvider_FavoriteToolsChanged(object? sender, EventArgs e) @@ -35,21 +27,6 @@ private void GuiToolProvider_FavoriteToolsChanged(object? sender, EventArgs e) RefreshJumpListAsync().Forget(); } - private void HandleOpenWithJumpList() - { - if (Environment.CommandLine.Contains(ToolArgument)) - { - string toolName = Environment.CommandLine.Substring(Environment.CommandLine.IndexOf(ToolArgument) + ToolArgument.Length); - GuiToolInstance? tool = _guiToolProvider.GetToolFromInternalName(toolName); - if (tool is not null) - { - // Make sure MainWindowViewModel is initialized. - _mefProvider.Import(); - Messenger.Send(new ChangeSelectedMenuItemMessage(tool)); - } - } - } - private Task RefreshJumpListAsync() { return ThreadHelper.RunOnUIThreadAsync(() => @@ -92,7 +69,7 @@ private static void AddToolToJumpList(JumpList jumpList, GuiToolInstance tool, s Title = tool.LongOrShortDisplayTitle, Description = tool.Description, CustomCategory = category, - Arguments = $"{ToolArgument}{tool.InternalComponentName}" + Arguments = $"{CommandLineLauncherService.ToolArgument}{tool.InternalComponentName}" }; jumpList.JumpItems.Add(jumpTask); diff --git a/src/app/dev/platforms/desktop/DevToys.Windows/MainWindow.xaml.cs b/src/app/dev/platforms/desktop/DevToys.Windows/MainWindow.xaml.cs index 5dcfc5dbb7..b62aad32d4 100644 --- a/src/app/dev/platforms/desktop/DevToys.Windows/MainWindow.xaml.cs +++ b/src/app/dev/platforms/desktop/DevToys.Windows/MainWindow.xaml.cs @@ -5,6 +5,7 @@ using DevToys.Blazor.BuiltInTools.ExtensionsManager; using DevToys.Blazor.Core.Languages; using DevToys.Blazor.Core.Services; +using DevToys.Business.Services; using DevToys.Business.ViewModels; using DevToys.Core; using DevToys.Core.Logging; @@ -193,6 +194,9 @@ private void InitializeLowPriorityServices() { // Start the Taskbar Jump List service after the web view loaded. _mefComposer.Provider.Import(); + + // Treat command line arguments. + _mefComposer.Provider.Import().HandleCommandLineArguments(); } private void SetPositionAndSize()