diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..052e1d225 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,26 @@ +# For more information, see: +# https://github.com/actions/stale +name: Mark stale issues and pull requests + +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v4 + with: + stale-issue-message: 'This issue is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 5 days.' + days-before-stale: 45 + days-before-close: 7 + days-before-pr-close: -1 + exempt-all-milestones: true + close-issue-message: 'This issue was closed because it has been stale for 7 days with no activity. If you feel this issue still needs attention please feel free to reopen.' + stale-pr-label: 'no-pr-activity' + exempt-issue-labels: 'keep-fresh' + exempt-pr-labels: 'keep-fresh,awaiting-approval,work-in-progress' diff --git a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs index c0cd022ea..fab1b3e8f 100644 --- a/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs +++ b/Flow.Launcher.Core/ExternalPlugins/PluginsManifest.cs @@ -2,6 +2,8 @@ using Flow.Launcher.Infrastructure.Logger; using System; using System.Collections.Generic; +using System.Net; +using System.Net.Http; using System.Text.Json; using System.Threading; using System.Threading.Tasks; @@ -10,43 +12,43 @@ namespace Flow.Launcher.Core.ExternalPlugins { public static class PluginsManifest { - static PluginsManifest() - { - UpdateTask = UpdateManifestAsync(); - } - - public static List UserPlugins { get; private set; } = new List(); - - public static Task UpdateTask { get; private set; } + private const string manifestFileUrl = "https://cdn.jsdelivr.net/gh/Flow-Launcher/Flow.Launcher.PluginsManifest@plugin_api_v2/plugins.json"; private static readonly SemaphoreSlim manifestUpdateLock = new(1); - public static Task UpdateManifestAsync() - { - if (manifestUpdateLock.CurrentCount == 0) - { - return UpdateTask; - } + private static string latestEtag = ""; - return UpdateTask = DownloadManifestAsync(); - } + public static List UserPlugins { get; private set; } = new List(); - private async static Task DownloadManifestAsync() + public static async Task UpdateManifestAsync(CancellationToken token = default) { try { - await manifestUpdateLock.WaitAsync().ConfigureAwait(false); + await manifestUpdateLock.WaitAsync(token).ConfigureAwait(false); - await using var jsonStream = await Http.GetStreamAsync("https://raw.githubusercontent.com/Flow-Launcher/Flow.Launcher.PluginsManifest/plugin_api_v2/plugins.json") - .ConfigureAwait(false); + var request = new HttpRequestMessage(HttpMethod.Get, manifestFileUrl); + request.Headers.Add("If-None-Match", latestEtag); - UserPlugins = await JsonSerializer.DeserializeAsync>(jsonStream).ConfigureAwait(false); + var response = await Http.SendAsync(request, token).ConfigureAwait(false); + + if (response.StatusCode == HttpStatusCode.OK) + { + Log.Info($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Fetched plugins from manifest repo"); + + var json = await response.Content.ReadAsStreamAsync(token).ConfigureAwait(false); + + UserPlugins = await JsonSerializer.DeserializeAsync>(json, cancellationToken: token).ConfigureAwait(false); + + latestEtag = response.Headers.ETag.Tag; + } + else if (response.StatusCode != HttpStatusCode.NotModified) + { + Log.Warn($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Http response for manifest file was {response.StatusCode}"); + } } catch (Exception e) { - Log.Exception("|PluginManagement.GetManifest|Encountered error trying to download plugins manifest", e); - - UserPlugins = new List(); + Log.Exception($"|PluginsManifest.{nameof(UpdateManifestAsync)}|Http request failed", e); } finally { diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index 60c4ec3de..fdd23a0d2 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -1,7 +1,7 @@  - net5.0-windows + net6.0-windows true true Library @@ -53,7 +53,7 @@ - + diff --git a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs index 0982e4017..049d1c583 100644 --- a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs +++ b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs @@ -1,5 +1,4 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -22,18 +21,23 @@ namespace Flow.Launcher.Core.Plugin RedirectStandardOutput = true, RedirectStandardError = true }; + + // required initialisation for below request calls + _startInfo.ArgumentList.Add(string.Empty); } protected override Task RequestAsync(JsonRPCRequestModel request, CancellationToken token = default) { - _startInfo.Arguments = $"\"{request}\""; + // since this is not static, request strings will build up in ArgumentList if index is not specified + _startInfo.ArgumentList[0] = request.ToString(); return ExecuteAsync(_startInfo, token); } protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default) { - _startInfo.Arguments = $"\"{rpcRequest}\""; + // since this is not static, request strings will build up in ArgumentList if index is not specified + _startInfo.ArgumentList[0] = rpcRequest.ToString(); return Execute(_startInfo); } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs index cf75e4aa3..e937779a1 100644 --- a/Flow.Launcher.Core/Plugin/JsonPRCModel.cs +++ b/Flow.Launcher.Core/Plugin/JsonPRCModel.cs @@ -93,4 +93,4 @@ namespace Flow.Launcher.Core.Plugin public Dictionary SettingsChange { get; set; } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 384418db9..1c0013db1 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -115,7 +115,7 @@ namespace Flow.Launcher.Core.Plugin foreach (var result in queryResponseModel.Result) { - result.Action = c => + result.AsyncAction = async c => { UpdateSettings(result.SettingsChange); @@ -133,15 +133,15 @@ namespace Flow.Launcher.Core.Plugin } else { - var actionResponse = Request(result.JsonRPCAction); + var actionResponse = await RequestAsync(result.JsonRPCAction); - if (string.IsNullOrEmpty(actionResponse)) + if (actionResponse.Length == 0) { return !result.JsonRPCAction.DontHideAfterAction; } - var jsonRpcRequestModel = - JsonSerializer.Deserialize(actionResponse, options); + var jsonRpcRequestModel = await + JsonSerializer.DeserializeAsync(actionResponse, options); if (jsonRpcRequestModel?.Method?.StartsWith("Flow.Launcher.") ?? false) { @@ -166,19 +166,20 @@ namespace Flow.Launcher.Core.Plugin private void ExecuteFlowLauncherAPI(string method, object[] parameters) { var parametersTypeArray = parameters.Select(param => param.GetType()).ToArray(); - MethodInfo methodInfo = PluginManager.API.GetType().GetMethod(method, parametersTypeArray); - if (methodInfo != null) + var methodInfo = typeof(IPublicAPI).GetMethod(method, parametersTypeArray); + if (methodInfo == null) + { + return; + } + try + { + methodInfo.Invoke(PluginManager.API, parameters); + } + catch (Exception) { - try - { - methodInfo.Invoke(PluginManager.API, parameters); - } - catch (Exception) - { #if (DEBUG) - throw; + throw; #endif - } } } @@ -241,7 +242,7 @@ namespace Flow.Launcher.Core.Plugin protected async Task ExecuteAsync(ProcessStartInfo startInfo, CancellationToken token = default) { Process process = null; - bool disposed = false; + using var exitTokenSource = new CancellationTokenSource(); try { process = Process.Start(startInfo); @@ -251,6 +252,7 @@ namespace Flow.Launcher.Core.Plugin return Stream.Null; } + await using var source = process.StandardOutput.BaseStream; var buffer = BufferManager.GetStream(); @@ -259,7 +261,7 @@ namespace Flow.Launcher.Core.Plugin { // ReSharper disable once AccessToModifiedClosure // Manually Check whether disposed - if (!disposed && !process.HasExited) + if (!exitTokenSource.IsCancellationRequested && !process.HasExited) process.Kill(); }); @@ -302,8 +304,8 @@ namespace Flow.Launcher.Core.Plugin } finally { + exitTokenSource.Cancel(); process?.Dispose(); - disposed = true; } } @@ -354,7 +356,9 @@ namespace Flow.Launcher.Core.Plugin this.context = context; await InitSettingAsync(); } - private static readonly Thickness settingControlMargin = new(10); + private static readonly Thickness settingControlMargin = new(10, 4, 10, 4); + private static readonly Thickness settingPanelMargin = new(15, 20, 15, 20); + private static readonly Thickness settingTextBlockMargin = new(10, 4, 10, 4); private JsonRpcConfigurationModel _settingsTemplate; public Control CreateSettingPanel() { @@ -363,8 +367,7 @@ namespace Flow.Launcher.Core.Plugin var settingWindow = new UserControl(); var mainPanel = new StackPanel { - Margin = settingControlMargin, - Orientation = Orientation.Vertical + Margin = settingPanelMargin, Orientation = Orientation.Vertical }; settingWindow.Content = mainPanel; @@ -372,13 +375,15 @@ namespace Flow.Launcher.Core.Plugin { var panel = new StackPanel { - Orientation = Orientation.Horizontal, - Margin = settingControlMargin + Orientation = Orientation.Horizontal, Margin = settingControlMargin }; - var name = new Label() + var name = new TextBlock() { - Content = attribute.Label, - Margin = settingControlMargin + Text = attribute.Label, + Width = 120, + VerticalAlignment = VerticalAlignment.Center, + Margin = settingControlMargin, + TextWrapping = TextWrapping.WrapWithOverflow }; FrameworkElement contentControl; @@ -390,8 +395,8 @@ namespace Flow.Launcher.Core.Plugin contentControl = new TextBlock { Text = attribute.Description.Replace("\\r\\n", "\r\n"), - Margin = settingControlMargin, - MaxWidth = 400, + Margin = settingTextBlockMargin, + MaxWidth = 500, TextWrapping = TextWrapping.WrapWithOverflow }; break; diff --git a/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs b/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs index 515b0bedc..9d76b6be0 100644 --- a/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginAssemblyLoader.cs @@ -15,20 +15,6 @@ namespace Flow.Launcher.Core.Plugin private readonly AssemblyName assemblyName; - private static readonly ConcurrentDictionary loadedAssembly; - - static PluginAssemblyLoader() - { - var currentAssemblies = AppDomain.CurrentDomain.GetAssemblies(); - loadedAssembly = new ConcurrentDictionary( - currentAssemblies.Select(x => new KeyValuePair(x.FullName, default))); - - AppDomain.CurrentDomain.AssemblyLoad += (sender, args) => - { - loadedAssembly[args.LoadedAssembly.FullName] = default; - }; - } - internal PluginAssemblyLoader(string assemblyFilePath) { dependencyResolver = new AssemblyDependencyResolver(assemblyFilePath); @@ -47,10 +33,9 @@ namespace Flow.Launcher.Core.Plugin // When resolving dependencies, ignore assembly depenedencies that already exits with Flow.Launcher // Otherwise duplicate assembly will be loaded and some weird behavior will occur, such as WinRT.Runtime.dll // will fail due to loading multiple versions in process, each with their own static instance of registration state - if (assemblyPath == null || ExistsInReferencedPackage(assemblyName)) - return null; + var existAssembly = Default.Assemblies.FirstOrDefault(x => x.FullName == assemblyName.FullName); - return LoadFromAssemblyPath(assemblyPath); + return existAssembly ?? (assemblyPath == null ? null : LoadFromAssemblyPath(assemblyPath)); } internal Type FromAssemblyGetTypeOfInterface(Assembly assembly, Type type) @@ -58,10 +43,5 @@ namespace Flow.Launcher.Core.Plugin var allTypes = assembly.ExportedTypes; return allTypes.First(o => o.IsClass && !o.IsAbstract && o.GetInterfaces().Any(t => t == type)); } - - internal bool ExistsInReferencedPackage(AssemblyName assemblyName) - { - return loadedAssembly.ContainsKey(assemblyName.FullName); - } } } \ No newline at end of file diff --git a/Flow.Launcher.Core/Plugin/QueryBuilder.cs b/Flow.Launcher.Core/Plugin/QueryBuilder.cs index ef387b693..a819f94b7 100644 --- a/Flow.Launcher.Core/Plugin/QueryBuilder.cs +++ b/Flow.Launcher.Core/Plugin/QueryBuilder.cs @@ -16,7 +16,7 @@ namespace Flow.Launcher.Core.Plugin return null; } - var rawQuery = string.Join(Query.TermSeparator, terms); + var rawQuery = text; string actionKeyword, search; string possibleActionKeyword = terms[0]; string[] searchTerms; @@ -24,13 +24,13 @@ namespace Flow.Launcher.Core.Plugin if (nonGlobalPlugins.TryGetValue(possibleActionKeyword, out var pluginPair) && !pluginPair.Metadata.Disabled) { // use non global plugin for query actionKeyword = possibleActionKeyword; - search = terms.Length > 1 ? rawQuery[(actionKeyword.Length + 1)..] : string.Empty; + search = terms.Length > 1 ? rawQuery[(actionKeyword.Length + 1)..].TrimStart() : string.Empty; searchTerms = terms[1..]; } else { // non action keyword actionKeyword = string.Empty; - search = rawQuery; + search = rawQuery.TrimStart(); searchTerms = terms; } diff --git a/Flow.Launcher.Core/Resource/AvailableLanguages.cs b/Flow.Launcher.Core/Resource/AvailableLanguages.cs index 0ad7ede1e..f541d3f35 100644 --- a/Flow.Launcher.Core/Resource/AvailableLanguages.cs +++ b/Flow.Launcher.Core/Resource/AvailableLanguages.cs @@ -19,6 +19,8 @@ namespace Flow.Launcher.Core.Resource public static Language Serbian = new Language("sr", "Srpski"); public static Language Portuguese_Portugal = new Language("pt-pt", "Português"); public static Language Portuguese_Brazil = new Language("pt-br", "Português (Brasil)"); + public static Language Spanish = new Language("es", "Spanish"); + public static Language Spanish_LatinAmerica = new Language("es-419", "Spanish (Latin America)"); public static Language Italian = new Language("it", "Italiano"); public static Language Norwegian_Bokmal = new Language("nb-NO", "Norsk Bokmål"); public static Language Slovak = new Language("sk", "Slovenský"); @@ -43,6 +45,8 @@ namespace Flow.Launcher.Core.Resource Serbian, Portuguese_Portugal, Portuguese_Brazil, + Spanish, + Spanish_LatinAmerica, Italian, Norwegian_Bokmal, Slovak, diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index 374f7c71f..5c99bc239 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -99,7 +99,7 @@ namespace Flow.Launcher.Core.Resource Settings.Language = language.LanguageCode; CultureInfo.CurrentCulture = new CultureInfo(language.LanguageCode); CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture; - Task.Run(() => + _ = Task.Run(() => { UpdatePluginMetadataTranslations(); }); @@ -182,6 +182,7 @@ namespace Flow.Launcher.Core.Resource { p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle(); p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription(); + pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture); } catch (Exception e) { diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 6561419a1..21892303a 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -85,10 +85,13 @@ namespace Flow.Launcher.Core.Resource Settings.Theme = theme; + // reload all resources even if the theme itself hasn't changed in order to pickup changes + // to things like fonts + UpdateResourceDictionary(GetResourceDictionary()); + //always allow re-loading default theme, in case of failure of switching to a new theme from default theme if (_oldTheme != theme || theme == defaultTheme) { - UpdateResourceDictionary(GetResourceDictionary()); _oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath); } diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index e09c6380c..69b537b39 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -91,8 +91,9 @@ namespace Flow.Launcher.Core catch (Exception e) when (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException) { Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e); - api.ShowMsg(api.GetTranslation("update_flowlauncher_fail"), - api.GetTranslation("update_flowlauncher_check_connection")); + if (!silentUpdate) + api.ShowMsg(api.GetTranslation("update_flowlauncher_fail"), + api.GetTranslation("update_flowlauncher_check_connection")); } finally { @@ -124,7 +125,7 @@ namespace Flow.Launcher.Core var releases = await System.Text.Json.JsonSerializer.DeserializeAsync>(jsonStream).ConfigureAwait(false); var latest = releases.Where(r => !r.Prerelease).OrderByDescending(r => r.PublishedAt).First(); var latestUrl = latest.HtmlUrl.Replace("/tag/", "/download/"); - + var client = new WebClient { Proxy = Http.WebProxy diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index cd49217a4..57b39e46e 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -21,7 +21,7 @@ namespace Flow.Launcher.Infrastructure public static readonly string PreinstalledDirectory = Path.Combine(ProgramDirectory, Plugins); public const string Issue = "https://github.com/Flow-Launcher/Flow.Launcher/issues/new"; public static readonly string Version = FileVersionInfo.GetVersionInfo(Assembly.Location.NonNull()).ProductVersion; - public const string Documentation = "https://flow-launcher.github.io/docs/#/usage-tips"; + public const string Documentation = "https://flowlauncher.com/docs/#/usage-tips"; public static readonly int ThumbnailSize = 64; private static readonly string ImagesDirectory = Path.Combine(ProgramDirectory, "Images"); @@ -43,8 +43,8 @@ namespace Flow.Launcher.Infrastructure public const string Settings = "Settings"; public const string Logs = "Logs"; - public const string Website = "https://flow-launcher.github.io"; + public const string Website = "https://flowlauncher.com"; public const string GitHub = "https://github.com/Flow-Launcher/Flow.Launcher"; - public const string Docs = "https://flow-launcher.github.io/docs"; + public const string Docs = "https://flowlauncher.com/docs"; } } diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index 40c2cb956..930cf0b91 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -1,7 +1,7 @@  - net5.0-windows + net6.0-windows {4FD29318-A8AB-4D8F-AA47-60BC241B8DA3} Library true diff --git a/Flow.Launcher.Infrastructure/Http/Http.cs b/Flow.Launcher.Infrastructure/Http/Http.cs index b45b6adcd..9f4146b7b 100644 --- a/Flow.Launcher.Infrastructure/Http/Http.cs +++ b/Flow.Launcher.Infrastructure/Http/Http.cs @@ -153,5 +153,13 @@ namespace Flow.Launcher.Infrastructure.Http var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, token); return await response.Content.ReadAsStreamAsync(); } + + /// + /// Asynchrously send an HTTP request. + /// + public static async Task SendAsync(HttpRequestMessage request, CancellationToken token = default) + { + return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token); + } } } diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 13277c7d9..04e11bf1a 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -74,7 +74,7 @@ namespace Flow.Launcher.Infrastructure.Image // To delete the images from the data dictionary based on the resizing of the Usage Dictionary // Double Check to avoid concurrent remove if (Data.Count > permissibleFactor * MaxCached) - foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key).ToArray()) + foreach (var key in Data.OrderBy(x => x.Value.usage).Take(Data.Count - MaxCached).Select(x => x.Key)) Data.TryRemove(key, out _); semaphore.Release(); } diff --git a/Flow.Launcher.Infrastructure/Logger/Log.cs b/Flow.Launcher.Infrastructure/Logger/Log.cs index 26e305ace..75f208c9e 100644 --- a/Flow.Launcher.Infrastructure/Logger/Log.cs +++ b/Flow.Launcher.Infrastructure/Logger/Log.cs @@ -211,4 +211,4 @@ namespace Flow.Launcher.Infrastructure.Logger LogInternal(message, LogLevel.Warn); } } -} \ No newline at end of file +} diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 7ce2fc8fd..41072993c 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -1,7 +1,7 @@ - + - net5.0-windows + net6.0-windows {8451ECDD-2EA4-4966-BB0A-7BBC40138E80} true Library @@ -14,10 +14,10 @@ - 2.1.0 - 2.1.0 - 2.1.0 - 2.1.0 + 3.0.0 + 3.0.0 + 3.0.0 + 3.0.0 Flow.Launcher.Plugin Flow-Launcher MIT @@ -42,6 +42,7 @@ 4 AnyCPU false + ..\Output\Debug\Flow.Launcher.Plugin.xml diff --git a/Flow.Launcher.Plugin/GlyphInfo.cs b/Flow.Launcher.Plugin/GlyphInfo.cs index d24624d8f..730046e1d 100644 --- a/Flow.Launcher.Plugin/GlyphInfo.cs +++ b/Flow.Launcher.Plugin/GlyphInfo.cs @@ -7,5 +7,10 @@ using System.Windows.Media; namespace Flow.Launcher.Plugin { + /// + /// Text with FontFamily specified + /// + /// Font Family of this Glyph + /// Text/Unicode of the Glyph public record GlyphInfo(string FontFamily, string Glyph); } diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 133ad25a5..69057820e 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -228,8 +228,27 @@ namespace Flow.Launcher.Plugin public void OpenDirectory(string DirectoryPath, string FileName = null); /// - /// Opens the url. The browser and mode used is based on what's configured in Flow's default browser settings. + /// Opens the URL with the given Uri object. + /// The browser and mode used is based on what's configured in Flow's default browser settings. + /// + public void OpenUrl(Uri url, bool? inPrivate = null); + + /// + /// Opens the URL with the given string. + /// The browser and mode used is based on what's configured in Flow's default browser settings. + /// Non-C# plugins should use this method. /// public void OpenUrl(string url, bool? inPrivate = null); + + /// + /// Opens the application URI with the given Uri object, e.g. obsidian://search-query-example + /// + public void OpenAppUri(Uri appUri); + + /// + /// Opens the application URI with the given string, e.g. obsidian://search-query-example + /// Non-C# plugins should use this method + /// + public void OpenAppUri(string appUri); } } diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index 29f8198ab..f80b6c028 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -1,6 +1,7 @@ -using System; +using System; using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; using System.Windows.Media; namespace Flow.Launcher.Plugin @@ -10,11 +11,11 @@ namespace Flow.Launcher.Plugin { private string _pluginDirectory; - + private string _icoPath; /// - /// Provides the title of the result. This is always required. + /// The title of the result. This is always required. /// public string Title { get; set; } @@ -29,6 +30,13 @@ namespace Flow.Launcher.Plugin /// public string ActionKeywordAssigned { get; set; } + /// + /// This holds the text which can be provided by plugin to be copied to the + /// user's clipboard when Ctrl + C is pressed on a result. If the text is a file/directory path + /// flow will copy the actual file/folder instead of just the path text. + /// + public string CopyText { get; set; } = string.Empty; + /// /// This holds the text which can be provided by plugin to help Flow autocomplete text /// for user on the plugin result. If autocomplete action for example is tab, pressing tab will have @@ -36,6 +44,11 @@ namespace Flow.Launcher.Plugin /// public string AutoCompleteText { get; set; } + /// + /// Image Displayed on the result + /// Relative Path to the Image File + /// GlyphInfo is prioritized if not null + /// public string IcoPath { get { return _icoPath; } @@ -60,16 +73,31 @@ namespace Flow.Launcher.Plugin public IconDelegate Icon; /// - /// Information for Glyph Icon + /// Information for Glyph Icon (Prioritized than IcoPath/Icon if user enable Glyph Icons) /// - public GlyphInfo Glyph { get; init; } + public GlyphInfo Glyph { get; init; } /// - /// return true to hide flowlauncher after select result + /// Delegate. An action to take in the form of a function call when the result has been selected + /// + /// true to hide flowlauncher after select result + /// /// public Func Action { get; set; } + /// + /// Delegate. An Async action to take in the form of a function call when the result has been selected + /// + /// true to hide flowlauncher after select result + /// + /// + public Func> AsyncAction { get; set; } + + /// + /// Priority of the current result + /// default: 0 + /// public int Score { get; set; } /// @@ -77,13 +105,11 @@ namespace Flow.Launcher.Plugin /// public IList TitleHighlightData { get; set; } - /// - /// A list of indexes for the characters to be highlighted in SubTitle - /// + [Obsolete("Deprecated as of Flow Launcher v1.9.1. Subtitle highlighting is no longer offered")] public IList SubTitleHighlightData { get; set; } /// - /// Only results that originQuery match with current query will be displayed in the panel + /// Query information associated with the result /// internal Query OriginQuery { get; set; } @@ -103,6 +129,7 @@ namespace Flow.Launcher.Plugin } } + /// public override bool Equals(object obj) { var r = obj as Result; @@ -110,12 +137,12 @@ namespace Flow.Launcher.Plugin var equality = string.Equals(r?.Title, Title) && string.Equals(r?.SubTitle, SubTitle) && string.Equals(r?.IcoPath, IcoPath) && - TitleHighlightData == r.TitleHighlightData && - SubTitleHighlightData == r.SubTitleHighlightData; + TitleHighlightData == r.TitleHighlightData; return equality; } + /// public override int GetHashCode() { var hashcode = (Title?.GetHashCode() ?? 0) ^ @@ -123,15 +150,17 @@ namespace Flow.Launcher.Plugin return hashcode; } + /// public override string ToString() { return Title + SubTitle; } - public Result() { } - /// - /// Additional data associate with this result + /// Additional data associated with this result + /// + /// As external information for ContextMenu + /// /// public object ContextData { get; set; } @@ -149,5 +178,10 @@ namespace Flow.Launcher.Plugin /// Show message as ToolTip on result SubTitle hover over /// public string SubTitleToolTip { get; set; } + + public ValueTask ExecuteAsync(ActionContext context) + { + return AsyncAction?.Invoke(context) ?? ValueTask.FromResult(Action?.Invoke(context) ?? false); + } } } \ No newline at end of file diff --git a/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs b/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs index 6c4ac8ebf..6588132b9 100644 --- a/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs +++ b/Flow.Launcher.Plugin/SharedCommands/SearchWeb.cs @@ -60,7 +60,7 @@ namespace Flow.Launcher.Plugin.SharedCommands try { - Process.Start(psi); + Process.Start(psi)?.Dispose(); } catch (System.ComponentModel.Win32Exception) { @@ -100,7 +100,7 @@ namespace Flow.Launcher.Plugin.SharedCommands psi.FileName = url; } - Process.Start(psi); + Process.Start(psi)?.Dispose(); } // This error may be thrown if browser path is incorrect catch (System.ComponentModel.Win32Exception) diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj index 8de0681c8..f429586ce 100644 --- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj +++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj @@ -1,7 +1,7 @@  - net5.0-windows10.0.19041.0 + net6.0-windows10.0.19041.0 {FF742965-9A80-41A5-B042-D6C7D3A21708} Library Properties diff --git a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs index 383650619..fb91c6388 100644 --- a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs +++ b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs @@ -48,7 +48,7 @@ namespace Flow.Launcher.Test.Plugins foreach (var result in results) { Assert.IsNotNull(result); - Assert.IsNotNull(result.Action); + Assert.IsNotNull(result.AsyncAction); Assert.IsNotNull(result.Title); } @@ -76,7 +76,7 @@ namespace Flow.Launcher.Test.Plugins [TestCaseSource(typeof(JsonRPCPluginTest), nameof(ResponseModelsSource))] public async Task GivenModel_WhenSerializeWithDifferentNamingPolicy_ThenExpectSameResult_Async(JsonRPCQueryResponseModel reference) { - var camelText = JsonSerializer.Serialize(reference, new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + var camelText = JsonSerializer.Serialize(reference, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); var pascalText = JsonSerializer.Serialize(reference); @@ -92,7 +92,7 @@ namespace Flow.Launcher.Test.Plugins Assert.AreEqual(result1, referenceResult); Assert.IsNotNull(result1); - Assert.IsNotNull(result1.Action); + Assert.IsNotNull(result1.AsyncAction); } } diff --git a/Flow.Launcher.Test/QueryBuilderTest.cs b/Flow.Launcher.Test/QueryBuilderTest.cs index 6090ecc65..45ff8fc9e 100644 --- a/Flow.Launcher.Test/QueryBuilderTest.cs +++ b/Flow.Launcher.Test/QueryBuilderTest.cs @@ -17,7 +17,7 @@ namespace Flow.Launcher.Test Query q = QueryBuilder.Build("> file.txt file2 file3", nonGlobalPlugins); - Assert.AreEqual("file.txt file2 file3", q.Search); + Assert.AreEqual("file.txt file2 file3", q.Search); Assert.AreEqual(">", q.ActionKeyword); } @@ -31,7 +31,7 @@ namespace Flow.Launcher.Test Query q = QueryBuilder.Build("> file.txt file2 file3", nonGlobalPlugins); - Assert.AreEqual("> file.txt file2 file3", q.Search); + Assert.AreEqual("> file.txt file2 file3", q.Search); } [Test] diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 9ee486b3b..8c970eddd 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.Text; using System.Threading.Tasks; @@ -69,8 +69,6 @@ namespace Flow.Launcher PluginManager.LoadPlugins(_settings.PluginSettings); _mainVM = new MainViewModel(_settings); - HotKeyMapper.Initialize(_mainVM); - API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet); Http.API = API; @@ -83,6 +81,8 @@ namespace Flow.Launcher Current.MainWindow = window; Current.MainWindow.Title = Constant.FlowLauncher; + + HotKeyMapper.Initialize(_mainVM); // happlebao todo temp fix for instance code logic // load plugin before change language, because plugin language also needs be changed @@ -104,14 +104,22 @@ namespace Flow.Launcher }); } - private void AutoStartup() { - if (_settings.StartFlowLauncherOnSystemStartup) + // we try to enable auto-startup on first launch, or reenable if it was removed + // but the user still has the setting set + if (_settings.StartFlowLauncherOnSystemStartup && !Helper.AutoStartup.IsEnabled) { - if (!SettingWindow.StartupSet()) + try { - SettingWindow.SetStartup(); + Helper.AutoStartup.Enable(); + } + catch (Exception e) + { + // but if it fails (permissions, etc) then don't keep retrying + // this also gives the user a visual indication in the Settings widget + _settings.StartFlowLauncherOnSystemStartup = false; + Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"), e.Message); } } } @@ -153,7 +161,6 @@ namespace Flow.Launcher DispatcherUnhandledException += ErrorReporting.DispatcherUnhandledException; } - /// /// let exception throw as normal is better for Debug /// @@ -179,4 +186,4 @@ namespace Flow.Launcher Current.MainWindow.Show(); } } -} \ No newline at end of file +} diff --git a/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs b/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs index 08ee8571e..ecdfc5851 100644 --- a/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs +++ b/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs @@ -53,7 +53,10 @@ namespace Flow.Launcher.Converters // Check if Text will be larger then our QueryTextBox System.Windows.Media.Typeface typeface = new Typeface(QueryTextBox.FontFamily, QueryTextBox.FontStyle, QueryTextBox.FontWeight, QueryTextBox.FontStretch); System.Windows.Media.FormattedText ft = new FormattedText(QueryTextBox.Text, System.Globalization.CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, typeface, QueryTextBox.FontSize, Brushes.Black); - if (ft.Width > QueryTextBox.ActualWidth || QueryTextBox.HorizontalOffset != 0) + + var offset = QueryTextBox.Padding.Right; + + if ((ft.Width + offset) > QueryTextBox.ActualWidth || QueryTextBox.HorizontalOffset != 0) { return string.Empty; }; diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml b/Flow.Launcher/CustomQueryHotkeySetting.xaml index 2f7d28212..187f99d18 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml @@ -79,20 +79,18 @@ - + - - + + - - + LastChildFill="True"> - + + - New Tab - New Window + + - + @@ -135,7 +135,7 @@ - + - + @@ -102,7 +99,7 @@ TargetType="{x:Type CheckBox}"> - + @@ -115,7 +112,12 @@ BasedOn="{StaticResource DefaultToggleSwitch}" TargetType="{x:Type ui:ToggleSwitch}"> - + + + + + + - - - + - - + + - - - - + @@ -152,7 +158,7 @@ - + @@ -160,44 +166,50 @@ - - + + - - + + - + - + @@ -249,12 +263,12 @@ - - + + - - - \ No newline at end of file diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index d785249a6..ebeee5e4a 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1,10 +1,9 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; -using System.Windows.Media; using System.Windows.Input; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Resource; @@ -20,6 +19,8 @@ using Flow.Launcher.Infrastructure.Logger; using Microsoft.VisualStudio.Threading; using System.Threading.Channels; using ISavable = Flow.Launcher.Plugin.ISavable; +using System.IO; +using System.Collections.Specialized; namespace Flow.Launcher.ViewModel { @@ -193,44 +194,45 @@ namespace Flow.Launcher.ViewModel PluginManager.API.OpenUrl("https://github.com/Flow-Launcher/Flow.Launcher/wiki/Flow-Launcher/"); }); OpenSettingCommand = new RelayCommand(_ => { App.API.OpenSettingDialog(); }); - OpenResultCommand = new RelayCommand(index => + OpenResultCommand = new RelayCommand(async index => { var results = SelectedResults; if (index != null) { - results.SelectedIndex = int.Parse(index.ToString()); + results.SelectedIndex = int.Parse(index.ToString()!); } var result = results.SelectedItem?.Result; - if (result != null) // SelectedItem returns null if selection is empty. + if (result == null) { - bool hideWindow = result.Action != null && result.Action(new ActionContext - { - SpecialKeyState = GlobalHotkey.CheckModifiers() - }); + return; + } + var hideWindow = await result.ExecuteAsync(new ActionContext + { + SpecialKeyState = GlobalHotkey.CheckModifiers() + }).ConfigureAwait(false); - if (hideWindow) - { - Hide(); - } + if (hideWindow) + { + Hide(); + } - if (SelectedIsFromQueryResults()) - { - _userSelectedRecord.Add(result); - _history.Add(result.OriginQuery.RawQuery); - } - else - { - SelectedResults = Results; - } + if (SelectedIsFromQueryResults()) + { + _userSelectedRecord.Add(result); + _history.Add(result.OriginQuery.RawQuery); + } + else + { + SelectedResults = Results; } }); AutocompleteQueryCommand = new RelayCommand(_ => { var result = SelectedResults.SelectedItem?.Result; - if (result != null) // SelectedItem returns null if selection is empty. + if (result != null && SelectedIsFromQueryResults()) // SelectedItem returns null if selection is empty. { var autoCompleteText = result.Title; @@ -253,6 +255,18 @@ namespace Flow.Launcher.ViewModel } }); + BackspaceCommand = new RelayCommand(index => + { + var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins); + + // GetPreviousExistingDirectory does not require trailing '\', otherwise will return empty string + var path = FilesFolders.GetPreviousExistingDirectory((_) => true, query.Search.TrimEnd('\\')); + + var actionKeyword = string.IsNullOrEmpty(query.ActionKeyword) ? string.Empty : $"{query.ActionKeyword} "; + + ChangeQueryText($"{actionKeyword}{path}"); + }); + LoadContextMenuCommand = new RelayCommand(_ => { if (SelectedIsFromQueryResults()) @@ -292,8 +306,8 @@ namespace Flow.Launcher.ViewModel { Notification.Show( InternationalizationManager.Instance.GetTranslation("success"), - InternationalizationManager.Instance.GetTranslation("completedSuccessfully"), - ""); + InternationalizationManager.Instance.GetTranslation("completedSuccessfully") + ); })) .ConfigureAwait(false); }); @@ -333,6 +347,9 @@ namespace Flow.Launcher.ViewModel { // re-query is done in QueryText's setter method QueryText = queryText; + // set to false so the subsequent set true triggers + // PropertyChanged and MoveQueryTextToEnd is called + QueryTextCursorMovedToEnd = false; } else if (reQuery) { @@ -392,9 +409,14 @@ namespace Flow.Launcher.ViewModel // because it is more accurate and reliable representation than using Visibility as a condition check public bool MainWindowVisibilityStatus { get; set; } = true; + public Visibility SearchIconVisibility { get; set; } + public double MainWindowWidth => _settings.WindowSize; + public string PluginIconPath { get; set; } = null; + public ICommand EscCommand { get; set; } + public ICommand BackspaceCommand { get; set; } public ICommand SelectNextItemCommand { get; set; } public ICommand SelectPrevItemCommand { get; set; } public ICommand SelectNextPageCommand { get; set; } @@ -407,6 +429,9 @@ namespace Flow.Launcher.ViewModel public ICommand OpenSettingCommand { get; set; } public ICommand ReloadPluginDataCommand { get; set; } public ICommand ClearQueryCommand { get; private set; } + + public ICommand CopyToClipboard { get; set; } + public ICommand AutocompleteQueryCommand { get; set; } public string OpenResultCommandModifiers { get; private set; } @@ -527,6 +552,8 @@ namespace Flow.Launcher.ViewModel { Results.Clear(); Results.Visbility = Visibility.Collapsed; + PluginIconPath = null; + SearchIconVisibility = Visibility.Visible; return; } @@ -546,7 +573,7 @@ namespace Flow.Launcher.ViewModel if (currentCancellationToken.IsCancellationRequested) return; - var query = QueryBuilder.Build(QueryText.Trim(), PluginManager.NonGlobalPlugins); + var query = QueryBuilder.Build(QueryText, PluginManager.NonGlobalPlugins); // handle the exclusiveness of plugin using action keyword RemoveOldQueryResults(query); @@ -555,6 +582,18 @@ namespace Flow.Launcher.ViewModel var plugins = PluginManager.ValidPluginsForQuery(query); + if (plugins.Count == 1) + { + PluginIconPath = plugins.Single().Metadata.IcoPath; + SearchIconVisibility = Visibility.Hidden; + } + else + { + PluginIconPath = null; + SearchIconVisibility = Visibility.Visible; + } + + if (query.ActionKeyword == Plugin.Query.GlobalPluginWildcardSign) { // Wait 45 millisecond for query change in global query @@ -626,7 +665,7 @@ namespace Flow.Launcher.ViewModel private void RemoveOldQueryResults(Query query) { - if (_lastQuery.ActionKeyword != query.ActionKeyword) + if (_lastQuery?.ActionKeyword != query?.ActionKeyword) { Results.Clear(); } @@ -645,7 +684,7 @@ namespace Flow.Launcher.ViewModel Action = _ => { _topMostRecord.Remove(result); - App.API.ShowMsg("Success"); + App.API.ShowMsg(InternationalizationManager.Instance.GetTranslation("success")); return false; } }; @@ -689,7 +728,11 @@ namespace Flow.Launcher.ViewModel IcoPath = icon, SubTitle = subtitle, PluginDirectory = metadata.PluginDirectory, - Action = _ => false + Action = _ => + { + App.API.OpenUrl(metadata.Website); + return true; + } }; return menu; } @@ -734,22 +777,10 @@ namespace Flow.Launcher.ViewModel public void Show() { string _explorerPath = FileExplorerHelper.GetActiveExplorerPath(); - - - if (_settings.UseSound) - { - MediaPlayer media = new MediaPlayer(); - media.Open(new Uri(AppDomain.CurrentDomain.BaseDirectory + "Resources\\open.wav")); - media.Play(); - } - MainWindowVisibility = Visibility.Visible; MainWindowVisibilityStatus = true; - - if(_settings.UseAnimation) - ((MainWindow)Application.Current.MainWindow).WindowAnimator(); - + MainWindowOpacity = 1; if (_explorerPath != null) { @@ -854,6 +885,52 @@ namespace Flow.Launcher.ViewModel Results.AddResults(resultsForUpdates, token); } + /// + /// This is the global copy method for an individual result. If no text is passed, + /// the method will work out what is to be copied based on the result, so plugin can offer the text + /// to be copied via the result model. If the text is a directory/file path, + /// then actual file/folder will be copied instead. + /// The result's subtitle text is the default text to be copied + /// + public void ResultCopy(string stringToCopy) + { + if (string.IsNullOrEmpty(stringToCopy)) + { + var result = Results.SelectedItem?.Result; + if (result != null) + { + string copyText = string.IsNullOrEmpty(result.CopyText) ? result.SubTitle : result.CopyText; + var isFile = File.Exists(copyText); + var isFolder = Directory.Exists(copyText); + if (isFile || isFolder) + { + var paths = new StringCollection(); + paths.Add(copyText); + + Clipboard.SetFileDropList(paths); + App.API.ShowMsg( + App.API.GetTranslation("copy") + +" " + + (isFile? App.API.GetTranslation("fileTitle") : App.API.GetTranslation("folderTitle")), + App.API.GetTranslation("completedSuccessfully")); + } + else + { + Clipboard.SetDataObject(copyText.ToString()); + App.API.ShowMsg( + App.API.GetTranslation("copy") + + " " + + App.API.GetTranslation("textTitle"), + App.API.GetTranslation("completedSuccessfully")); + } + } + + return; + } + + Clipboard.SetDataObject(stringToCopy); + } + #endregion } -} \ No newline at end of file +} diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index 209198822..738bd0ec4 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -55,4 +55,4 @@ namespace Flow.Launcher.ViewModel public static bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword); } -} \ No newline at end of file +} diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 908de0c09..4d32d792d 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -7,11 +7,16 @@ using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using System.IO; +using System.Drawing.Text; +using System.Collections.Generic; namespace Flow.Launcher.ViewModel { public class ResultViewModel : BaseModel { + private static PrivateFontCollection fontCollection = new(); + private static Dictionary fonts = new(); + public ResultViewModel(Result result, Settings settings) { if (result != null) @@ -23,13 +28,29 @@ namespace Flow.Launcher.ViewModel // Checks if it's a system installed font, which does not require path to be provided. if (glyph.FontFamily.EndsWith(".ttf") || glyph.FontFamily.EndsWith(".otf")) { - var fontPath = Result.Glyph.FontFamily; - Glyph = Path.IsPathRooted(fontPath) - ? Result.Glyph - : Result.Glyph with + string fontFamilyPath = glyph.FontFamily; + + if (!Path.IsPathRooted(fontFamilyPath)) + { + fontFamilyPath = Path.Combine(Result.PluginDirectory, fontFamilyPath); + } + + if (fonts.ContainsKey(fontFamilyPath)) + { + Glyph = glyph with { - FontFamily = Path.Combine(Result.PluginDirectory, fontPath) + FontFamily = fonts[fontFamilyPath] }; + } + else + { + fontCollection.AddFontFile(fontFamilyPath); + fonts[fontFamilyPath] = $"{Path.GetDirectoryName(fontFamilyPath)}/#{fontCollection.Families[^1].Name}"; + Glyph = glyph with + { + FontFamily = fonts[fontFamilyPath] + }; + } } else { diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index 01a19d871..db24825d0 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -310,4 +310,4 @@ namespace Flow.Launcher.ViewModel } } } -} \ No newline at end of file +} diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs index 342c85da2..a63e54f3b 100644 --- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs +++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -60,7 +60,30 @@ namespace Flow.Launcher.ViewModel Settings.AutoUpdates = value; if (value) + { UpdateApp(); + } + } + } + + public bool StartFlowLauncherOnSystemStartup + { + get => Settings.StartFlowLauncherOnSystemStartup; + set + { + Settings.StartFlowLauncherOnSystemStartup = value; + + try + { + if (value) + AutoStartup.Enable(); + else + AutoStartup.Disable(); + } + catch (Exception e) + { + Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"), e.Message); + } } } @@ -273,7 +296,7 @@ namespace Flow.Launcher.ViewModel #region theme - public static string Theme => @"https://flow-launcher.github.io/docs/#/how-to-create-a-theme"; + public static string Theme => @"https://flowlauncher.com/docs/#/how-to-create-a-theme"; public string SelectedTheme { diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index d7b412392..735530520 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -37,7 +37,8 @@ namespace Flow.Launcher.Plugin.BrowserBookmark return new(); foreach (var folder in rootElement.EnumerateObject()) { - EnumerateFolderBookmark(folder.Value, bookmarks, source); + if (folder.Value.ValueKind == JsonValueKind.Object) + EnumerateFolderBookmark(folder.Value, bookmarks, source); } return bookmarks; } @@ -64,4 +65,4 @@ namespace Flow.Launcher.Plugin.BrowserBookmark } } -} \ No newline at end of file +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj index 2a586818c..8454b11e6 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -2,7 +2,7 @@ Library - net5.0-windows + net6.0-windows true {9B130CC5-14FB-41FF-B310-0A95B6894C37} Properties diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/bookmark.png b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/bookmark.png index b8aee3564..d68cecea1 100644 Binary files a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/bookmark.png and b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/bookmark.png differ diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png index 0dee870b6..3218c94c9 100644 Binary files a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png and b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Images/copylink.png differ diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/da.xaml new file mode 100644 index 000000000..86c09730c --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/da.xaml @@ -0,0 +1,22 @@ + + + + + Browser Bookmarks + Search your browser bookmarks + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Tilføj + Slet + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/de.xaml new file mode 100644 index 000000000..0f8227530 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/de.xaml @@ -0,0 +1,22 @@ + + + + + Browser-Lesezeichen + Lesezeichen durchsuchen + + + Lesezeichen-Daten + Lesezeichen öffnen in: + Neues Fenster + Neuer Tab + Browser aus Pfad festlegen: + Auswählen + Kopiere Url + Die Url des Lesezeichens in die Zwischenablage kopieren + Lade Browser-Lesezeichen aus: + Browser-Name + Pfad zum Datenverzeichnis + Hinzufügen + Löschen + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml index af2a556c1..8f5396dd7 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/en.xaml @@ -8,7 +8,7 @@ Search your browser bookmarks - Bookmmark Data + Bookmark Data Open bookmarks in: New window New tab diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/es-419.xaml new file mode 100644 index 000000000..b22481631 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/es-419.xaml @@ -0,0 +1,22 @@ + + + + + Marcadores del Navegador + Busca en los marcadores de tu navegador + + + Datos de Marcadores + Abrir marcadores en: + Nueva ventana + Nueva pestaña + Establecer navegador desde ruta: + Elegir + Copiar url + Copiar la url del marcador al portapapeles + Cargar navegador desde: + Nombre del Navegador + Ruta del Directorio de Datos + Añadir + Eliminar + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/es.xaml new file mode 100644 index 000000000..190679ea4 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/es.xaml @@ -0,0 +1,22 @@ + + + + + Marcadores del navegador + Busca en los marcadores de tu navegador + + + Datos de marcador + Abrir marcadores en: + Nueva ventana + Nueva Pestaña + Establecer navegador desde ruta: + Elige + Copiar enlace + Copiar enlace del marcador al portapapeles + Iniciar navegador desde: + Nombre del navegador + Ruta del directorio de datos + Añadir + Eliminar + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/fr.xaml new file mode 100644 index 000000000..485092912 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/fr.xaml @@ -0,0 +1,22 @@ + + + + + Browser Bookmarks + Search your browser bookmarks + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Ajouter + Supprimer + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/it.xaml new file mode 100644 index 000000000..f0f2d79bb --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/it.xaml @@ -0,0 +1,22 @@ + + + + + Browser Bookmarks + Search your browser bookmarks + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Aggiungi + Cancella + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ja.xaml new file mode 100644 index 000000000..232007a4d --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ja.xaml @@ -0,0 +1,22 @@ + + + + + Browser Bookmarks + Search your browser bookmarks + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + + 削除 + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ko.xaml new file mode 100644 index 000000000..6f916cf54 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ko.xaml @@ -0,0 +1,22 @@ + + + + + 브라우저 북마크 + 브라우저의 북마크 검색 + + + 북마크 데이터 + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + 브라우저 이름 + 데이터 디렉토리 위치 + + 삭제 + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/nb.xaml new file mode 100644 index 000000000..c5d6f77a0 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/nb.xaml @@ -0,0 +1,22 @@ + + + + + Browser Bookmarks + Search your browser bookmarks + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Add + Delete + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/nl.xaml new file mode 100644 index 000000000..d1cbaa001 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/nl.xaml @@ -0,0 +1,22 @@ + + + + + Browser Bookmarks + Search your browser bookmarks + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Toevoegen + Verwijder + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pl.xaml new file mode 100644 index 000000000..024232350 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pl.xaml @@ -0,0 +1,22 @@ + + + + + Browser Bookmarks + Search your browser bookmarks + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Dodaj + Usu + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-br.xaml new file mode 100644 index 000000000..db29166a0 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-br.xaml @@ -0,0 +1,22 @@ + + + + + Browser Bookmarks + Search your browser bookmarks + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Adicionar + Apagar + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-pt.xaml index 259f34c85..eefcc1d2e 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-pt.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/pt-pt.xaml @@ -1,19 +1,20 @@  - - Marcadores do navegador - Pesquisar nos marcadores do navegador + + Marcadores do navegador + Pesquisar nos marcadores do navegador - + + Dados do marcador Abrir marcadores em: Nova janela Novo separador Caminho do navegador: Escolher Copiar URL - Copiar URL do marcador para área de transferência - Carregar navegador de: + Copiar URL do marcador para a área de transferência + Carregar navegador em: Nome do navegador Caminho do diretório de dados Adicionar diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ru.xaml new file mode 100644 index 000000000..545ddbf9a --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/ru.xaml @@ -0,0 +1,22 @@ + + + + + Browser Bookmarks + Search your browser bookmarks + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Добавить + Удалить + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sk.xaml index 76fc24883..b45b437a8 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sk.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sk.xaml @@ -1,11 +1,12 @@  - - Prehliadač záložiek - Vyhľadáva záložky prehliadača + + Záložky prehliadača + Vyhľadáva záložky prehliadača - + + Nastavenia pluginu Otvoriť záložky v: Nové okno Nová karta @@ -15,7 +16,7 @@ Kopírovať URL záložky do schránky Načítať prehliadač z: Názov prehliadača - Cesta k dátovému priečinku + Umiestnenie priečinku s dátami Pridať Odstrániť diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sr.xaml new file mode 100644 index 000000000..d898a834c --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/sr.xaml @@ -0,0 +1,22 @@ + + + + + Browser Bookmarks + Search your browser bookmarks + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Dodaj + Obriši + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/tr.xaml index dbfad64f4..3e18a245e 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/tr.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/tr.xaml @@ -1,9 +1,22 @@ - - - - Yer İşaretleri - Tarayıcılarınızdaki yer işaretlerini arayın. - - \ No newline at end of file + + + + + Yer İşaretleri + Tarayıcılarınızdaki yer işaretlerini arayın. + + + Yer İmleri Verisi + Open bookmarks in: + Yeni Pencere + Yeni Sekme + Dizin üzerinden tarayıcı seç: + Seç + Url Kopyala + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Ekle + Sil + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/uk-UA.xaml new file mode 100644 index 000000000..f8701ed49 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/uk-UA.xaml @@ -0,0 +1,22 @@ + + + + + Browser Bookmarks + Search your browser bookmarks + + + Bookmark Data + Open bookmarks in: + New window + New tab + Set browser from path: + Choose + Copy url + Copy the bookmark's url to clipboard + Load Browser From: + Browser Name + Data Directory Path + Додати + Видалити + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/zh-cn.xaml index 288e3ed94..81fa84b44 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/zh-cn.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/zh-cn.xaml @@ -1,17 +1,22 @@ - - - - 浏览器书签 - 搜索您的浏览器书签 - - - 在以下位置打开书签: - 新窗口 - 新标签 - 设置浏览器路径: - 选择 - 复制网址 - 将书签的网址复制到剪贴板 - \ No newline at end of file + + + + + 浏览器书签 + 搜索您的浏览器书签 + + + 书签数据 + 在以下位置打开书签: + 新窗口 + 新标签 + 设置浏览器路径: + 选择 + 复制网址 + 将书签的网址复制到剪贴板 + 从以下浏览器加载: + 浏览器名称 + 数据文件路径 + 增加 + 删除 + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/zh-tw.xaml new file mode 100644 index 000000000..a847b8704 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/zh-tw.xaml @@ -0,0 +1,22 @@ + + + + + 瀏覽器書籤 + 搜索你的瀏覽器書籤 + + + 書籤資料 + 載入書籤至: + 新增視窗 + 新增分頁 + 設定瀏覽器路徑: + 選擇 + 複製網址 + 複製書籤網址 + 載入瀏覽器自: + 瀏覽器名稱 + 檔案目錄路徑 + 新增 + 刪除 + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml index 8a2a65f26..4d23bd060 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml @@ -5,11 +5,12 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:Flow.Launcher.Plugin.BrowserBookmark.Models" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - Title="{DynamicResource BookmarkDataSetting}" - Width="500" + Title="{DynamicResource flowlauncher_plugin_browserbookmark_bookmarkDataSetting}" + Width="520" Background="{DynamicResource PopuBGColor}" Foreground="{DynamicResource PopupTextColor}" KeyDown="WindowKeyDown" + ResizeMode="NoResize" SizeToContent="Height" WindowStartupLocation="CenterScreen" mc:Ignorable="d"> @@ -66,39 +67,54 @@ FontFamily="Segoe UI" FontSize="20" FontWeight="SemiBold" - Text="{DynamicResource BookmarkDataSetting}" + Text="{DynamicResource flowlauncher_plugin_browserbookmark_bookmarkDataSetting}" TextAlignment="Left" /> - - - - - - - + + + + + + + + + + + + + + + @@ -110,15 +126,13 @@