diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs index 8412ba7e8..944b2fd10 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginSettings.cs @@ -1,18 +1,14 @@ using System.Collections.Concurrent; using System.Collections.Generic; +using System.Text.Json; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; -using System.Windows.Forms; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Plugin; -using CheckBox = System.Windows.Controls.CheckBox; -using ComboBox = System.Windows.Controls.ComboBox; -using Control = System.Windows.Controls.Control; -using Orientation = System.Windows.Controls.Orientation; -using TextBox = System.Windows.Controls.TextBox; -using UserControl = System.Windows.Controls.UserControl; + +#nullable enable namespace Flow.Launcher.Core.Plugin { @@ -23,51 +19,72 @@ namespace Flow.Launcher.Core.Plugin public required string SettingPath { get; init; } public Dictionary SettingControls { get; } = new(); - public IReadOnlyDictionary Inner => Settings; - protected ConcurrentDictionary Settings { get; set; } + public IReadOnlyDictionary Inner => Settings; + protected ConcurrentDictionary Settings { get; set; } = null!; public required IPublicAPI API { get; init; } - private JsonStorage> _storage; + private JsonStorage> _storage = null!; - // maybe move to resource? - private static readonly Thickness settingControlMargin = new(0, 9, 18, 9); - private static readonly Thickness settingCheckboxMargin = new(0, 9, 9, 9); - private static readonly Thickness settingPanelMargin = new(0, 0, 0, 0); - private static readonly Thickness settingTextBlockMargin = new(70, 9, 18, 9); - private static readonly Thickness settingLabelPanelMargin = new(70, 9, 18, 9); - private static readonly Thickness settingLabelMargin = new(0, 0, 0, 0); - private static readonly Thickness settingDescMargin = new(0, 2, 0, 0); - private static readonly Thickness settingSepMargin = new(0, 0, 0, 2); + private static readonly Thickness SettingPanelMargin = (Thickness)Application.Current.FindResource("SettingPanelMargin"); + private static readonly Thickness SettingPanelItemLeftMargin = (Thickness)Application.Current.FindResource("SettingPanelItemLeftMargin"); + private static readonly Thickness SettingPanelItemTopBottomMargin = (Thickness)Application.Current.FindResource("SettingPanelItemTopBottomMargin"); + private static readonly Thickness SettingPanelItemLeftTopBottomMargin = (Thickness)Application.Current.FindResource("SettingPanelItemLeftTopBottomMargin"); + private static readonly double SettingPanelTextBoxMinWidth = (double)Application.Current.FindResource("SettingPanelTextBoxMinWidth"); + private static readonly double SettingPanelPathTextBoxWidth = (double)Application.Current.FindResource("SettingPanelPathTextBoxWidth"); + private static readonly double SettingPanelAreaTextBoxMinHeight = (double)Application.Current.FindResource("SettingPanelAreaTextBoxMinHeight"); public async Task InitializeAsync() { - _storage = new JsonStorage>(SettingPath); - Settings = await _storage.LoadAsync(); - - if (Configuration == null) + if (Settings == null) { - return; + _storage = new JsonStorage>(SettingPath); + Settings = await _storage.LoadAsync(); + + // Because value type of settings dictionary is object which causes them to be JsonElement when loading from json files, + // we need to convert it to the correct type + foreach (var (key, value) in Settings) + { + if (value is not JsonElement jsonElement) continue; + + Settings[key] = jsonElement.ValueKind switch + { + JsonValueKind.String => jsonElement.GetString() ?? value, + JsonValueKind.True => jsonElement.GetBoolean(), + JsonValueKind.False => jsonElement.GetBoolean(), + JsonValueKind.Null => null, + _ => value + }; + } } + if (Configuration == null) return; + foreach (var (type, attributes) in Configuration.Body) { - if (attributes.Name == null) - { - continue; - } + // Skip if the setting does not have attributes or name + if (attributes?.Name == null) continue; - if (!Settings.ContainsKey(attributes.Name)) + // Skip if the setting does not have attributes or name + if (!NeedSaveInSettings(type)) continue; + + // If need save in settings, we need to make sure the setting exists in the settings file + if (Settings.ContainsKey(attributes.Name)) continue; + + if (type == "checkbox") + { + // If can parse the default value to bool, use it, otherwise use false + Settings[attributes.Name] = bool.TryParse(attributes.DefaultValue, out var value) && value; + } + else { Settings[attributes.Name] = attributes.DefaultValue; } } } - public void UpdateSettings(IReadOnlyDictionary settings) { - if (settings == null || settings.Count == 0) - return; + if (settings == null || settings.Count == 0) return; foreach (var (key, value) in settings) { @@ -78,19 +95,23 @@ namespace Flow.Launcher.Core.Plugin switch (control) { case TextBox textBox: - textBox.Dispatcher.Invoke(() => textBox.Text = value as string ?? string.Empty); + var text = value as string ?? string.Empty; + textBox.Dispatcher.Invoke(() => textBox.Text = text); break; case PasswordBox passwordBox: - passwordBox.Dispatcher.Invoke(() => passwordBox.Password = value as string ?? string.Empty); + var password = value as string ?? string.Empty; + passwordBox.Dispatcher.Invoke(() => passwordBox.Password = password); break; case ComboBox comboBox: comboBox.Dispatcher.Invoke(() => comboBox.SelectedItem = value); break; case CheckBox checkBox: - checkBox.Dispatcher.Invoke(() => - checkBox.IsChecked = value is bool isChecked - ? isChecked - : bool.Parse(value as string ?? string.Empty)); + var isChecked = value is bool boolValue + ? boolValue + // If can parse the default value to bool, use it, otherwise use false + : value is string stringValue && bool.TryParse(stringValue, out var boolValueFromString) + && boolValueFromString; + checkBox.Dispatcher.Invoke(() =>checkBox.IsChecked = isChecked); break; } } @@ -108,7 +129,7 @@ namespace Flow.Launcher.Core.Plugin { _storage.Save(); } - + public bool NeedCreateSettingPanel() { // If there are no settings or the settings configuration is empty, return null @@ -118,329 +139,350 @@ namespace Flow.Launcher.Core.Plugin public Control CreateSettingPanel() { // No need to check if NeedCreateSettingPanel is true because CreateSettingPanel will only be called if it's true + // if (!NeedCreateSettingPanel()) return null; - var settingWindow = new UserControl(); - var mainPanel = new Grid { Margin = settingPanelMargin, VerticalAlignment = VerticalAlignment.Center }; - - ColumnDefinition gridCol1 = new ColumnDefinition(); - ColumnDefinition gridCol2 = new ColumnDefinition(); - - gridCol1.Width = new GridLength(70, GridUnitType.Star); - gridCol2.Width = new GridLength(30, GridUnitType.Star); - mainPanel.ColumnDefinitions.Add(gridCol1); - mainPanel.ColumnDefinitions.Add(gridCol2); - settingWindow.Content = mainPanel; - int rowCount = 0; - - foreach (var (type, attribute) in Configuration.Body) + // Create main grid with two columns (Column 1: Auto, Column 2: *) + var mainPanel = new Grid { Margin = SettingPanelMargin, VerticalAlignment = VerticalAlignment.Center }; + mainPanel.ColumnDefinitions.Add(new ColumnDefinition() { - Separator sep = new Separator(); - sep.VerticalAlignment = VerticalAlignment.Top; - sep.Margin = settingSepMargin; - sep.SetResourceReference(Separator.BackgroundProperty, "Color03B"); /* for theme change */ - var panel = new StackPanel + Width = new GridLength(0, GridUnitType.Auto) + }); + mainPanel.ColumnDefinitions.Add(new ColumnDefinition() + { + Width = new GridLength(1, GridUnitType.Star) + }); + + // Iterate over each setting and create one row for it + var rowCount = 0; + foreach (var (type, attributes) in Configuration!.Body) + { + // Skip if the setting does not have attributes or name + if (attributes?.Name == null) continue; + + // Add a new row to the main grid + mainPanel.RowDefinitions.Add(new RowDefinition() { - Orientation = Orientation.Vertical, - VerticalAlignment = VerticalAlignment.Center, - Margin = settingLabelPanelMargin - }; - - RowDefinition gridRow = new RowDefinition(); - mainPanel.RowDefinitions.Add(gridRow); - var name = new TextBlock() - { - Text = attribute.Label, - VerticalAlignment = VerticalAlignment.Center, - Margin = settingLabelMargin, - TextWrapping = TextWrapping.WrapWithOverflow - }; - - var desc = new TextBlock() - { - Text = attribute.Description, - FontSize = 12, - VerticalAlignment = VerticalAlignment.Center, - Margin = settingDescMargin, - TextWrapping = TextWrapping.WrapWithOverflow - }; - - desc.SetResourceReference(TextBlock.ForegroundProperty, "Color04B"); - - if (attribute.Description == null) /* if no description, hide */ - desc.Visibility = Visibility.Collapsed; - - - if (type != "textBlock") /* if textBlock, hide desc */ - { - panel.Children.Add(name); - panel.Children.Add(desc); - } - - - Grid.SetColumn(panel, 0); - Grid.SetRow(panel, rowCount); + Height = new GridLength(0, GridUnitType.Auto) + }); + // State controls for column 0 and 1 + StackPanel? panel = null; FrameworkElement contentControl; + // If the type is textBlock, separator, or checkbox, we do not need to create a panel + if (type != "textBlock" && type != "separator" && type != "checkbox") + { + // Create a panel to hold the label and description + panel = new StackPanel + { + Margin = SettingPanelItemTopBottomMargin, + Orientation = Orientation.Vertical, + VerticalAlignment = VerticalAlignment.Center + }; + + // Create a text block for name + var name = new TextBlock() + { + Text = attributes.Label, + VerticalAlignment = VerticalAlignment.Center, + TextWrapping = TextWrapping.WrapWithOverflow + }; + + // Create a text block for description + TextBlock? desc = null; + if (attributes.Description != null) + { + desc = new TextBlock() + { + Text = attributes.Description, + VerticalAlignment = VerticalAlignment.Center, + TextWrapping = TextWrapping.WrapWithOverflow + }; + + desc.SetResourceReference(TextBlock.StyleProperty, "SettingPanelTextBlockDescriptionStyle"); // for theme change + } + + // Add the name and description to the panel + panel.Children.Add(name); + if (desc != null) panel.Children.Add(desc); + } + switch (type) { case "textBlock": - { - contentControl = new TextBlock { - Text = attribute.Description.Replace("\\r\\n", "\r\n"), - Margin = settingTextBlockMargin, - Padding = new Thickness(0, 0, 0, 0), - HorizontalAlignment = System.Windows.HorizontalAlignment.Left, - TextAlignment = TextAlignment.Left, - TextWrapping = TextWrapping.Wrap - }; + contentControl = new TextBlock + { + Text = attributes.Description?.Replace("\\r\\n", "\r\n") ?? string.Empty, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, + Margin = SettingPanelItemTopBottomMargin, + TextAlignment = TextAlignment.Left, + TextWrapping = TextWrapping.Wrap + }; - Grid.SetColumn(contentControl, 0); - Grid.SetColumnSpan(contentControl, 2); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - - break; - } + break; + } case "input": - { - var textBox = new TextBox() { - Text = Settings[attribute.Name] as string ?? string.Empty, - Margin = settingControlMargin, - HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, - ToolTip = attribute.Description - }; + var textBox = new TextBox() + { + MinWidth = SettingPanelTextBoxMinWidth, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, + Margin = SettingPanelItemLeftTopBottomMargin, + Text = Settings[attributes.Name] as string ?? string.Empty, + ToolTip = attributes.Description + }; - textBox.TextChanged += (_, _) => - { - Settings[attribute.Name] = textBox.Text; - }; + textBox.TextChanged += (_, _) => + { + Settings[attributes.Name] = textBox.Text; + }; - contentControl = textBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); + contentControl = textBox; - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - - break; - } + break; + } case "inputWithFileBtn": case "inputWithFolderBtn": - { - var textBox = new TextBox() { - Margin = new Thickness(10, 0, 0, 0), - Text = Settings[attribute.Name] as string ?? string.Empty, - HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, - ToolTip = attribute.Description - }; - - textBox.TextChanged += (_, _) => - { - Settings[attribute.Name] = textBox.Text; - }; - - var Btn = new System.Windows.Controls.Button() - { - Margin = new Thickness(10, 0, 0, 0), Content = "Browse" - }; - - Btn.Click += (_, _) => - { - using CommonDialog dialog = type switch + var textBox = new TextBox() { - "inputWithFolderBtn" => new FolderBrowserDialog(), - _ => new OpenFileDialog(), + Width = SettingPanelPathTextBoxWidth, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, + Margin = SettingPanelItemLeftMargin, + Text = Settings[attributes.Name] as string ?? string.Empty, + ToolTip = attributes.Description }; - if (dialog.ShowDialog() != DialogResult.OK) return; - var path = dialog switch + textBox.TextChanged += (_, _) => { - FolderBrowserDialog folderDialog => folderDialog.SelectedPath, - OpenFileDialog fileDialog => fileDialog.FileName, + Settings[attributes.Name] = textBox.Text; }; - textBox.Text = path; - Settings[attribute.Name] = path; - }; - var dockPanel = new DockPanel() { Margin = settingControlMargin }; + var Btn = new Button() + { + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, + Margin = SettingPanelItemLeftMargin, + Content = API.GetTranslation("select") + }; - DockPanel.SetDock(Btn, Dock.Right); - dockPanel.Children.Add(Btn); - dockPanel.Children.Add(textBox); - contentControl = dockPanel; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); + Btn.Click += (_, _) => + { + using System.Windows.Forms.CommonDialog dialog = type switch + { + "inputWithFolderBtn" => new System.Windows.Forms.FolderBrowserDialog(), + _ => new System.Windows.Forms.OpenFileDialog(), + }; - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); + if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) + { + return; + } - break; - } + var path = dialog switch + { + System.Windows.Forms.FolderBrowserDialog folderDialog => folderDialog.SelectedPath, + System.Windows.Forms.OpenFileDialog fileDialog => fileDialog.FileName, + _ => throw new System.NotImplementedException() + }; + + textBox.Text = path; + Settings[attributes.Name] = path; + }; + + var stackPanel = new StackPanel() + { + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, + Margin = SettingPanelItemTopBottomMargin, + Orientation = Orientation.Horizontal + }; + + // Create a stack panel to wrap the button and text box + stackPanel.Children.Add(textBox); + stackPanel.Children.Add(Btn); + + contentControl = stackPanel; + + break; + } case "textarea": - { - var textBox = new TextBox() { - Height = 120, - Margin = settingControlMargin, - VerticalAlignment = VerticalAlignment.Center, - TextWrapping = TextWrapping.WrapWithOverflow, - AcceptsReturn = true, - HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, - Text = Settings[attribute.Name] as string ?? string.Empty, - ToolTip = attribute.Description - }; + var textBox = new TextBox() + { + MinHeight = SettingPanelAreaTextBoxMinHeight, + HorizontalAlignment = HorizontalAlignment.Stretch, + VerticalAlignment = VerticalAlignment.Center, + Margin = SettingPanelItemLeftTopBottomMargin, + TextWrapping = TextWrapping.WrapWithOverflow, + AcceptsReturn = true, + Text = Settings[attributes.Name] as string ?? string.Empty, + ToolTip = attributes.Description + }; - textBox.TextChanged += (sender, _) => - { - Settings[attribute.Name] = ((TextBox)sender).Text; - }; + textBox.TextChanged += (sender, _) => + { + Settings[attributes.Name] = ((TextBox)sender).Text; + }; - contentControl = textBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); + contentControl = textBox; - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - - break; - } + break; + } case "passwordBox": - { - var passwordBox = new PasswordBox() { - Margin = settingControlMargin, - Password = Settings[attribute.Name] as string ?? string.Empty, - PasswordChar = attribute.passwordChar == default ? '*' : attribute.passwordChar, - HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, - ToolTip = attribute.Description - }; + var passwordBox = new PasswordBox() + { + MinWidth = SettingPanelTextBoxMinWidth, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, + Margin = SettingPanelItemLeftTopBottomMargin, + Password = Settings[attributes.Name] as string ?? string.Empty, + PasswordChar = attributes.passwordChar == default ? '*' : attributes.passwordChar, + ToolTip = attributes.Description, + }; - passwordBox.PasswordChanged += (sender, _) => - { - Settings[attribute.Name] = ((PasswordBox)sender).Password; - }; + passwordBox.PasswordChanged += (sender, _) => + { + Settings[attributes.Name] = ((PasswordBox)sender).Password; + }; - contentControl = passwordBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); + contentControl = passwordBox; - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - - break; - } + break; + } case "dropdown": - { - var comboBox = new System.Windows.Controls.ComboBox() { - ItemsSource = attribute.Options, - SelectedItem = Settings[attribute.Name], - Margin = settingControlMargin, - HorizontalAlignment = System.Windows.HorizontalAlignment.Right, - ToolTip = attribute.Description - }; + var comboBox = new ComboBox() + { + ItemsSource = attributes.Options, + SelectedItem = Settings[attributes.Name], + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, + Margin = SettingPanelItemLeftTopBottomMargin, + ToolTip = attributes.Description + }; - comboBox.SelectionChanged += (sender, _) => - { - Settings[attribute.Name] = (string)((System.Windows.Controls.ComboBox)sender).SelectedItem; - }; + comboBox.SelectionChanged += (sender, _) => + { + Settings[attributes.Name] = (string)((ComboBox)sender).SelectedItem; + }; - contentControl = comboBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); + contentControl = comboBox; - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - - break; - } + break; + } case "checkbox": - var checkBox = new CheckBox { - IsChecked = - Settings[attribute.Name] is bool isChecked + // If can parse the default value to bool, use it, otherwise use false + var defaultValue = bool.TryParse(attributes.DefaultValue, out var value) && value; + var checkBox = new CheckBox + { + IsChecked = + Settings[attributes.Name] is bool isChecked ? isChecked - : bool.Parse(attribute.DefaultValue), - Margin = settingCheckboxMargin, - HorizontalAlignment = System.Windows.HorizontalAlignment.Right, - ToolTip = attribute.Description - }; + : defaultValue, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, + Margin = SettingPanelItemTopBottomMargin, + Content = attributes.Label, + ToolTip = attributes.Description + }; - checkBox.Click += (sender, _) => - { - Settings[attribute.Name] = ((CheckBox)sender).IsChecked; - }; + checkBox.Click += (sender, _) => + { + Settings[attributes.Name] = ((CheckBox)sender).IsChecked ?? defaultValue; + }; - contentControl = checkBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); + contentControl = checkBox; - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - - break; + break; + } case "hyperlink": - var hyperlink = new Hyperlink { ToolTip = attribute.Description, NavigateUri = attribute.url }; - - var linkbtn = new System.Windows.Controls.Button { - HorizontalAlignment = System.Windows.HorizontalAlignment.Right, - Margin = settingControlMargin - }; + var hyperlink = new Hyperlink + { + ToolTip = attributes.Description, + NavigateUri = attributes.url + }; - linkbtn.Content = attribute.urlLabel; + hyperlink.Inlines.Add(attributes.urlLabel); + hyperlink.RequestNavigate += (sender, e) => + { + API.OpenUrl(e.Uri); + e.Handled = true; + }; - contentControl = linkbtn; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); + var textBlock = new TextBlock() + { + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Center, + Margin = SettingPanelItemLeftTopBottomMargin, + TextAlignment = TextAlignment.Left, + TextWrapping = TextWrapping.Wrap + }; + textBlock.Inlines.Add(hyperlink); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); + contentControl = textBlock; - break; + break; + } + case "separator": + { + var sep = new Separator(); + + sep.SetResourceReference(Separator.StyleProperty, "SettingPanelSeparatorStyle"); + + contentControl = sep; + + break; + } default: continue; } - if (type != "textBlock") - SettingControls[attribute.Name] = contentControl; + // If type is textBlock or separator, we just add the content control to the main grid + if (panel == null) + { + // Add the content control to the column 0, row rowCount and columnSpan 2 of the main grid + mainPanel.Children.Add(contentControl); + Grid.SetColumn(contentControl, 0); + Grid.SetColumnSpan(contentControl, 2); + Grid.SetRow(contentControl, rowCount); + } + else + { + // Add the panel to the column 0 and row rowCount of the main grid + mainPanel.Children.Add(panel); + Grid.SetColumn(panel, 0); + Grid.SetRow(panel, rowCount); + + // Add the content control to the column 1 and row rowCount of the main grid + mainPanel.Children.Add(contentControl); + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + } + + // Add into SettingControls for settings storage if need + if (NeedSaveInSettings(type)) SettingControls[attributes.Name] = contentControl; - mainPanel.Children.Add(panel); - mainPanel.Children.Add(contentControl); rowCount++; } - return settingWindow; + // Wrap the main grid in a user control + return new UserControl() + { + Content = mainPanel + }; + } + + private static bool NeedSaveInSettings(string type) + { + return type != "textBlock" && type != "separator" && type != "hyperlink"; } } } diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 09711051e..aab3caa40 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -205,9 +205,6 @@ namespace Flow.Launcher.Core.Plugin } } - InternationalizationManager.Instance.AddPluginLanguageDirectories(GetPluginsForInterface()); - InternationalizationManager.Instance.ChangeLanguage(Ioc.Default.GetRequiredService().Language); - if (failedPlugins.Any()) { var failed = string.Join(",", failedPlugins.Select(x => x.Metadata.Name)); @@ -231,7 +228,6 @@ namespace Flow.Launcher.Core.Plugin if (!NonGlobalPlugins.ContainsKey(query.ActionKeyword)) return GlobalPlugins; - var plugin = NonGlobalPlugins[query.ActionKeyword]; return new List { @@ -367,7 +363,16 @@ namespace Flow.Launcher.Core.Plugin NonGlobalPlugins[newActionKeyword] = plugin; } + // Update action keywords and action keyword in plugin metadata plugin.Metadata.ActionKeywords.Add(newActionKeyword); + if (plugin.Metadata.ActionKeywords.Count > 0) + { + plugin.Metadata.ActionKeyword = plugin.Metadata.ActionKeywords[0]; + } + else + { + plugin.Metadata.ActionKeyword = string.Empty; + } } /// @@ -388,16 +393,15 @@ namespace Flow.Launcher.Core.Plugin if (oldActionkeyword != Query.GlobalPluginWildcardSign) NonGlobalPlugins.Remove(oldActionkeyword); - + // Update action keywords and action keyword in plugin metadata plugin.Metadata.ActionKeywords.Remove(oldActionkeyword); - } - - public static void ReplaceActionKeyword(string id, string oldActionKeyword, string newActionKeyword) - { - if (oldActionKeyword != newActionKeyword) + if (plugin.Metadata.ActionKeywords.Count > 0) { - AddActionKeyword(id, newActionKeyword); - RemoveActionKeyword(id, oldActionKeyword); + plugin.Metadata.ActionKeyword = plugin.Metadata.ActionKeywords[0]; + } + else + { + plugin.Metadata.ActionKeyword = string.Empty; } } diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index e2a66656a..ffa17ab4d 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -67,9 +67,9 @@ namespace Flow.Launcher.Core.Resource return DefaultLanguageCode; } - internal void AddPluginLanguageDirectories(IEnumerable plugins) + private void AddPluginLanguageDirectories() { - foreach (var plugin in plugins) + foreach (var plugin in PluginManager.GetPluginsForInterface()) { var location = Assembly.GetAssembly(plugin.Plugin.GetType()).Location; var dir = Path.GetDirectoryName(location); @@ -96,6 +96,32 @@ namespace Flow.Launcher.Core.Resource _oldResources.Clear(); } + /// + /// Initialize language. Will change app language and plugin language based on settings. + /// + public async Task InitializeLanguageAsync() + { + // Get actual language + var languageCode = _settings.Language; + if (languageCode == Constant.SystemLanguageCode) + { + languageCode = SystemLanguageCode; + } + + // Get language by language code and change language + var language = GetLanguageByLanguageCode(languageCode); + + // Add plugin language directories first so that we can load language files from plugins + AddPluginLanguageDirectories(); + + // Change language + await ChangeLanguageAsync(language); + } + + /// + /// Change language during runtime. Will change app language and plugin language & save settings. + /// + /// public void ChangeLanguage(string languageCode) { languageCode = languageCode.NonNull(); @@ -110,7 +136,12 @@ namespace Flow.Launcher.Core.Resource // Get language by language code and change language var language = GetLanguageByLanguageCode(languageCode); - ChangeLanguage(language, isSystem); + + // Change language + _ = ChangeLanguageAsync(language); + + // Save settings + _settings.Language = isSystem ? Constant.SystemLanguageCode : language.LanguageCode; } private Language GetLanguageByLanguageCode(string languageCode) @@ -128,26 +159,22 @@ namespace Flow.Launcher.Core.Resource } } - private void ChangeLanguage(Language language, bool isSystem) + private async Task ChangeLanguageAsync(Language language) { - language = language.NonNull(); - + // Remove old language files and load language RemoveOldLanguageFiles(); if (language != AvailableLanguages.English) { LoadLanguage(language); } + // Culture of main thread // Use CreateSpecificCulture to preserve possible user-override settings in Windows, if Flow's language culture is the same as Windows's CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture(language.LanguageCode); CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture; - // Raise event after culture is set - _settings.Language = isSystem ? Constant.SystemLanguageCode : language.LanguageCode; - _ = Task.Run(() => - { - UpdatePluginMetadataTranslations(); - }); + // Raise event for plugins after culture is set + await Task.Run(UpdatePluginMetadataTranslations); } public bool PromptShouldUsePinyin(string languageCodeToSet) diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index cda125a39..489002617 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -3,21 +3,29 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml; +using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; +using System.Windows.Controls.Primitives; using System.Windows.Markup; using System.Windows.Media; using System.Windows.Media.Effects; using System.Windows.Shell; +using System.Windows.Threading; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; +using Microsoft.Win32; namespace Flow.Launcher.Core.Resource { public class Theme { + #region Properties & Fields + + public bool BlurEnabled { get; set; } + private const string ThemeMetadataNamePrefix = "Name:"; private const string ThemeMetadataIsDarkPrefix = "IsDark:"; private const string ThemeMetadataHasBlurPrefix = "HasBlur:"; @@ -31,14 +39,12 @@ namespace Flow.Launcher.Core.Resource private string _oldTheme; private const string Folder = Constant.Themes; private const string Extension = ".xaml"; - private string DirectoryPath => Path.Combine(Constant.ProgramDirectory, Folder); - private string UserDirectoryPath => Path.Combine(DataLocation.DataDirectory(), Folder); + private static string DirectoryPath => Path.Combine(Constant.ProgramDirectory, Folder); + private static string UserDirectoryPath => Path.Combine(DataLocation.DataDirectory(), Folder); - public string CurrentTheme => _settings.Theme; + #endregion - public bool BlurEnabled { get; set; } - - private double mainWindowWidth; + #region Constructor public Theme(IPublicAPI publicAPI, Settings settings) { @@ -50,20 +56,32 @@ namespace Flow.Launcher.Core.Resource MakeSureThemeDirectoriesExist(); var dicts = Application.Current.Resources.MergedDictionaries; - _oldResource = dicts.First(d => + _oldResource = dicts.FirstOrDefault(d => { - if (d.Source == null) - return false; + if (d.Source == null) return false; var p = d.Source.AbsolutePath; - var dir = Path.GetDirectoryName(p).NonNull(); - var info = new DirectoryInfo(dir); - var f = info.Name; - var e = Path.GetExtension(p); - var found = f == Folder && e == Extension; - return found; + return p.Contains(Folder) && Path.GetExtension(p) == Extension; }); - _oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath); + + if (_oldResource != null) + { + _oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath); + } + else + { + Log.Error("Current theme resource not found. Initializing with default theme."); + _oldTheme = Constant.DefaultTheme; + }; + } + + #endregion + + #region Theme Resources + + public string GetCurrentTheme() + { + return _settings.Theme; } private void MakeSureThemeDirectoriesExist() @@ -81,68 +99,154 @@ namespace Flow.Launcher.Core.Resource } } - public bool ChangeTheme(string theme) - { - const string defaultTheme = Constant.DefaultTheme; - - string path = GetThemePath(theme); - try - { - if (string.IsNullOrEmpty(path)) - throw new DirectoryNotFoundException("Theme path can't be found <{path}>"); - - // reload all resources even if the theme itself hasn't changed in order to pickup changes - // to things like fonts - UpdateResourceDictionary(GetResourceDictionary(theme)); - - _settings.Theme = theme; - - - //always allow re-loading default theme, in case of failure of switching to a new theme from default theme - if (_oldTheme != theme || theme == defaultTheme) - { - _oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath); - } - - BlurEnabled = Win32Helper.IsBlurTheme(); - - if (_settings.UseDropShadowEffect && !BlurEnabled) - AddDropShadowEffectToCurrentTheme(); - - Win32Helper.SetBlurForWindow(Application.Current.MainWindow, BlurEnabled); - } - catch (DirectoryNotFoundException) - { - Log.Error($"|Theme.ChangeTheme|Theme <{theme}> path can't be found"); - if (theme != defaultTheme) - { - _api.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_path_not_exists"), theme)); - ChangeTheme(defaultTheme); - } - return false; - } - catch (XamlParseException) - { - Log.Error($"|Theme.ChangeTheme|Theme <{theme}> fail to parse"); - if (theme != defaultTheme) - { - _api.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("theme_load_failure_parse_error"), theme)); - ChangeTheme(defaultTheme); - } - return false; - } - return true; - } - private void UpdateResourceDictionary(ResourceDictionary dictionaryToUpdate) { - var dicts = Application.Current.Resources.MergedDictionaries; + // Add new resources + if (!Application.Current.Resources.MergedDictionaries.Contains(dictionaryToUpdate)) + { + Application.Current.Resources.MergedDictionaries.Add(dictionaryToUpdate); + } + + // Remove old resources + if (_oldResource != null && _oldResource != dictionaryToUpdate && + Application.Current.Resources.MergedDictionaries.Contains(_oldResource)) + { + Application.Current.Resources.MergedDictionaries.Remove(_oldResource); + } - dicts.Remove(_oldResource); - dicts.Add(dictionaryToUpdate); _oldResource = dictionaryToUpdate; } + /// + /// Updates only the font settings and refreshes the UI. + /// + public void UpdateFonts() + { + try + { + // Load a ResourceDictionary for the specified theme. + var themeName = GetCurrentTheme(); + var dict = GetThemeResourceDictionary(themeName); + + // Apply font settings to the theme resource. + ApplyFontSettings(dict); + UpdateResourceDictionary(dict); + + // Must apply blur and drop shadow effects + _ = RefreshFrameAsync(); + } + catch (Exception e) + { + Log.Exception("Error occurred while updating theme fonts", e); + } + } + + /// + /// Loads and applies font settings to the theme resource. + /// + private void ApplyFontSettings(ResourceDictionary dict) + { + if (dict["QueryBoxStyle"] is Style queryBoxStyle && + dict["QuerySuggestionBoxStyle"] is Style querySuggestionBoxStyle) + { + var fontFamily = new FontFamily(_settings.QueryBoxFont); + var fontStyle = FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.QueryBoxFontStyle); + var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.QueryBoxFontWeight); + var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.QueryBoxFontStretch); + + SetFontProperties(queryBoxStyle, fontFamily, fontStyle, fontWeight, fontStretch, true); + SetFontProperties(querySuggestionBoxStyle, fontFamily, fontStyle, fontWeight, fontStretch, false); + } + + if (dict["ItemTitleStyle"] is Style resultItemStyle && + dict["ItemTitleSelectedStyle"] is Style resultItemSelectedStyle && + dict["ItemHotkeyStyle"] is Style resultHotkeyItemStyle && + dict["ItemHotkeySelectedStyle"] is Style resultHotkeyItemSelectedStyle) + { + var fontFamily = new FontFamily(_settings.ResultFont); + var fontStyle = FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.ResultFontStyle); + var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.ResultFontWeight); + var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.ResultFontStretch); + + SetFontProperties(resultItemStyle, fontFamily, fontStyle, fontWeight, fontStretch, false); + SetFontProperties(resultItemSelectedStyle, fontFamily, fontStyle, fontWeight, fontStretch, false); + SetFontProperties(resultHotkeyItemStyle, fontFamily, fontStyle, fontWeight, fontStretch, false); + SetFontProperties(resultHotkeyItemSelectedStyle, fontFamily, fontStyle, fontWeight, fontStretch, false); + } + + if (dict["ItemSubTitleStyle"] is Style resultSubItemStyle && + dict["ItemSubTitleSelectedStyle"] is Style resultSubItemSelectedStyle) + { + var fontFamily = new FontFamily(_settings.ResultSubFont); + var fontStyle = FontHelper.GetFontStyleFromInvariantStringOrNormal(_settings.ResultSubFontStyle); + var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.ResultSubFontWeight); + var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.ResultSubFontStretch); + + SetFontProperties(resultSubItemStyle, fontFamily, fontStyle, fontWeight, fontStretch, false); + SetFontProperties(resultSubItemSelectedStyle, fontFamily, fontStyle, fontWeight, fontStretch, false); + } + } + + /// + /// Applies font properties to a Style. + /// + private static void SetFontProperties(Style style, FontFamily fontFamily, FontStyle fontStyle, FontWeight fontWeight, FontStretch fontStretch, bool isTextBox) + { + // Remove existing font-related setters + if (isTextBox) + { + // First, find the setters to remove and store them in a list + var settersToRemove = style.Setters + .OfType() + .Where(setter => + setter.Property == Control.FontFamilyProperty || + setter.Property == Control.FontStyleProperty || + setter.Property == Control.FontWeightProperty || + setter.Property == Control.FontStretchProperty) + .ToList(); + + // Remove each found setter one by one + foreach (var setter in settersToRemove) + { + style.Setters.Remove(setter); + } + + // Add New font setter + style.Setters.Add(new Setter(Control.FontFamilyProperty, fontFamily)); + style.Setters.Add(new Setter(Control.FontStyleProperty, fontStyle)); + style.Setters.Add(new Setter(Control.FontWeightProperty, fontWeight)); + style.Setters.Add(new Setter(Control.FontStretchProperty, fontStretch)); + + // Set caret brush (retain existing logic) + var caretBrushPropertyValue = style.Setters.OfType().Any(x => x.Property.Name == "CaretBrush"); + var foregroundPropertyValue = style.Setters.OfType().Where(x => x.Property.Name == "Foreground") + .Select(x => x.Value).FirstOrDefault(); + if (!caretBrushPropertyValue && foregroundPropertyValue != null) + style.Setters.Add(new Setter(TextBoxBase.CaretBrushProperty, foregroundPropertyValue)); + } + else + { + var settersToRemove = style.Setters + .OfType() + .Where(setter => + setter.Property == TextBlock.FontFamilyProperty || + setter.Property == TextBlock.FontStyleProperty || + setter.Property == TextBlock.FontWeightProperty || + setter.Property == TextBlock.FontStretchProperty) + .ToList(); + + foreach (var setter in settersToRemove) + { + style.Setters.Remove(setter); + } + + style.Setters.Add(new Setter(TextBlock.FontFamilyProperty, fontFamily)); + style.Setters.Add(new Setter(TextBlock.FontStyleProperty, fontStyle)); + style.Setters.Add(new Setter(TextBlock.FontWeightProperty, fontWeight)); + style.Setters.Add(new Setter(TextBlock.FontStretchProperty, fontStretch)); + } + } + private ResourceDictionary GetThemeResourceDictionary(string theme) { var uri = GetThemePath(theme); @@ -154,9 +258,7 @@ namespace Flow.Launcher.Core.Resource return dict; } - private ResourceDictionary CurrentThemeResourceDictionary() => GetThemeResourceDictionary(_settings.Theme); - - public ResourceDictionary GetResourceDictionary(string theme) + private ResourceDictionary GetResourceDictionary(string theme) { var dict = GetThemeResourceDictionary(theme); @@ -168,22 +270,22 @@ namespace Flow.Launcher.Core.Resource var fontWeight = FontHelper.GetFontWeightFromInvariantStringOrNormal(_settings.QueryBoxFontWeight); var fontStretch = FontHelper.GetFontStretchFromInvariantStringOrNormal(_settings.QueryBoxFontStretch); - queryBoxStyle.Setters.Add(new Setter(TextBox.FontFamilyProperty, fontFamily)); - queryBoxStyle.Setters.Add(new Setter(TextBox.FontStyleProperty, fontStyle)); - queryBoxStyle.Setters.Add(new Setter(TextBox.FontWeightProperty, fontWeight)); - queryBoxStyle.Setters.Add(new Setter(TextBox.FontStretchProperty, fontStretch)); + queryBoxStyle.Setters.Add(new Setter(Control.FontFamilyProperty, fontFamily)); + queryBoxStyle.Setters.Add(new Setter(Control.FontStyleProperty, fontStyle)); + queryBoxStyle.Setters.Add(new Setter(Control.FontWeightProperty, fontWeight)); + queryBoxStyle.Setters.Add(new Setter(Control.FontStretchProperty, fontStretch)); var caretBrushPropertyValue = queryBoxStyle.Setters.OfType().Any(x => x.Property.Name == "CaretBrush"); var foregroundPropertyValue = queryBoxStyle.Setters.OfType().Where(x => x.Property.Name == "Foreground") .Select(x => x.Value).FirstOrDefault(); if (!caretBrushPropertyValue && foregroundPropertyValue != null) //otherwise BaseQueryBoxStyle will handle styling - queryBoxStyle.Setters.Add(new Setter(TextBox.CaretBrushProperty, foregroundPropertyValue)); + queryBoxStyle.Setters.Add(new Setter(TextBoxBase.CaretBrushProperty, foregroundPropertyValue)); // Query suggestion box's font style is aligned with query box - querySuggestionBoxStyle.Setters.Add(new Setter(TextBox.FontFamilyProperty, fontFamily)); - querySuggestionBoxStyle.Setters.Add(new Setter(TextBox.FontStyleProperty, fontStyle)); - querySuggestionBoxStyle.Setters.Add(new Setter(TextBox.FontWeightProperty, fontWeight)); - querySuggestionBoxStyle.Setters.Add(new Setter(TextBox.FontStretchProperty, fontStretch)); + querySuggestionBoxStyle.Setters.Add(new Setter(Control.FontFamilyProperty, fontFamily)); + querySuggestionBoxStyle.Setters.Add(new Setter(Control.FontStyleProperty, fontStyle)); + querySuggestionBoxStyle.Setters.Add(new Setter(Control.FontWeightProperty, fontWeight)); + querySuggestionBoxStyle.Setters.Add(new Setter(Control.FontStretchProperty, fontStretch)); } if (dict["ItemTitleStyle"] is Style resultItemStyle && @@ -213,36 +315,20 @@ namespace Flow.Launcher.Core.Resource Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch }; Array.ForEach( - new[] { resultSubItemStyle,resultSubItemSelectedStyle}, o + new[] { resultSubItemStyle, resultSubItemSelectedStyle }, o => Array.ForEach(setters, p => o.Setters.Add(p))); } /* Ignore Theme Window Width and use setting */ var windowStyle = dict["WindowStyle"] as Style; var width = _settings.WindowSize; - windowStyle.Setters.Add(new Setter(Window.WidthProperty, width)); - mainWindowWidth = (double)width; + windowStyle.Setters.Add(new Setter(FrameworkElement.WidthProperty, width)); return dict; } - private ResourceDictionary GetCurrentResourceDictionary( ) + private ResourceDictionary GetCurrentResourceDictionary() { - return GetResourceDictionary(_settings.Theme); - } - - public List LoadAvailableThemes() - { - List themes = new List(); - foreach (var themeDirectory in _themeDirectories) - { - var filePaths = Directory - .GetFiles(themeDirectory) - .Where(filePath => filePath.EndsWith(Extension) && !filePath.EndsWith("Base.xaml")) - .Select(GetThemeDataFromPath); - themes.AddRange(filePaths); - } - - return themes.OrderBy(o => o.Name).ToList(); + return GetResourceDictionary(GetCurrentTheme()); } private ThemeData GetThemeDataFromPath(string path) @@ -264,15 +350,15 @@ namespace Flow.Launcher.Core.Resource { if (line.StartsWith(ThemeMetadataNamePrefix, StringComparison.OrdinalIgnoreCase)) { - name = line.Remove(0, ThemeMetadataNamePrefix.Length).Trim(); + name = line[ThemeMetadataNamePrefix.Length..].Trim(); } else if (line.StartsWith(ThemeMetadataIsDarkPrefix, StringComparison.OrdinalIgnoreCase)) { - isDark = bool.Parse(line.Remove(0, ThemeMetadataIsDarkPrefix.Length).Trim()); + isDark = bool.Parse(line[ThemeMetadataIsDarkPrefix.Length..].Trim()); } else if (line.StartsWith(ThemeMetadataHasBlurPrefix, StringComparison.OrdinalIgnoreCase)) { - hasBlur = bool.Parse(line.Remove(0, ThemeMetadataHasBlurPrefix.Length).Trim()); + hasBlur = bool.Parse(line[ThemeMetadataHasBlurPrefix.Length..].Trim()); } } @@ -293,6 +379,82 @@ namespace Flow.Launcher.Core.Resource return string.Empty; } + #endregion + + #region Load & Change + + public List LoadAvailableThemes() + { + List themes = new List(); + foreach (var themeDirectory in _themeDirectories) + { + var filePaths = Directory + .GetFiles(themeDirectory) + .Where(filePath => filePath.EndsWith(Extension) && !filePath.EndsWith("Base.xaml")) + .Select(GetThemeDataFromPath); + themes.AddRange(filePaths); + } + + return themes.OrderBy(o => o.Name).ToList(); + } + + public bool ChangeTheme(string theme = null) + { + if (string.IsNullOrEmpty(theme)) + theme = GetCurrentTheme(); + + string path = GetThemePath(theme); + try + { + if (string.IsNullOrEmpty(path)) + throw new DirectoryNotFoundException($"Theme path can't be found <{path}>"); + + // Retrieve theme resource – always use the resource with font settings applied. + var resourceDict = GetResourceDictionary(theme); + + UpdateResourceDictionary(resourceDict); + + _settings.Theme = theme; + + //always allow re-loading default theme, in case of failure of switching to a new theme from default theme + if (_oldTheme != theme || theme == Constant.DefaultTheme) + { + _oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath); + } + + BlurEnabled = IsBlurTheme(); + + // Can only apply blur but here also apply drop shadow effect to avoid possible drop shadow effect issues + _ = RefreshFrameAsync(); + + return true; + } + catch (DirectoryNotFoundException) + { + Log.Error($"|Theme.ChangeTheme|Theme <{theme}> path can't be found"); + if (theme != Constant.DefaultTheme) + { + _api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_path_not_exists"), theme)); + ChangeTheme(Constant.DefaultTheme); + } + return false; + } + catch (XamlParseException) + { + Log.Error($"|Theme.ChangeTheme|Theme <{theme}> fail to parse"); + if (theme != Constant.DefaultTheme) + { + _api.ShowMsgBox(string.Format(_api.GetTranslation("theme_load_failure_parse_error"), theme)); + ChangeTheme(Constant.DefaultTheme); + } + return false; + } + } + + #endregion + + #region Shadow Effect + public void AddDropShadowEffectToCurrentTheme() { var dict = GetCurrentResourceDictionary(); @@ -311,8 +473,7 @@ namespace Flow.Launcher.Core.Resource } }; - var marginSetter = windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) as Setter; - if (marginSetter == null) + if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) is not Setter marginSetter) { var margin = new Thickness(ShadowExtraMargin, 12, ShadowExtraMargin, ShadowExtraMargin); marginSetter = new Setter() @@ -347,14 +508,12 @@ namespace Flow.Launcher.Core.Resource var dict = GetCurrentResourceDictionary(); var windowBorderStyle = dict["WindowBorderStyle"] as Style; - var effectSetter = windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.EffectProperty) as Setter; - var marginSetter = windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) as Setter; - - if (effectSetter != null) + if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.EffectProperty) is Setter effectSetter) { windowBorderStyle.Setters.Remove(effectSetter); } - if (marginSetter != null) + + if (windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.MarginProperty) is Setter marginSetter) { var currentMargin = (Thickness)marginSetter.Value; var newMargin = new Thickness( @@ -395,6 +554,340 @@ namespace Flow.Launcher.Core.Resource } } + #endregion + + #region Blur Handling + + /// + /// Refreshes the frame to apply the current theme settings. + /// + public async Task RefreshFrameAsync() + { + await Application.Current.Dispatcher.InvokeAsync(() => + { + // Get the actual backdrop type and drop shadow effect settings + var (backdropType, useDropShadowEffect) = GetActualValue(); + + // Remove OS minimizing/maximizing animation + // Methods.SetWindowAttribute(new WindowInteropHelper(mainWindow).Handle, DWMWINDOWATTRIBUTE.DWMWA_TRANSITIONS_FORCEDISABLED, 3); + + // The timing of adding the shadow effect should vary depending on whether the theme is transparent. + if (BlurEnabled) + { + AutoDropShadow(useDropShadowEffect); + } + SetBlurForWindow(GetCurrentTheme(), backdropType); + + if (!BlurEnabled) + { + AutoDropShadow(useDropShadowEffect); + } + }, DispatcherPriority.Normal); + } + + /// + /// Sets the blur for a window via SetWindowCompositionAttribute + /// + public async Task SetBlurForWindowAsync() + { + await Application.Current.Dispatcher.InvokeAsync(() => + { + // Get the actual backdrop type and drop shadow effect settings + var (backdropType, _) = GetActualValue(); + + SetBlurForWindow(GetCurrentTheme(), backdropType); + }, DispatcherPriority.Normal); + } + + /// + /// Gets the actual backdrop type and drop shadow effect settings based on the current theme status. + /// + public (BackdropTypes BackdropType, bool UseDropShadowEffect) GetActualValue() + { + var backdropType = _settings.BackdropType; + var useDropShadowEffect = _settings.UseDropShadowEffect; + + // When changed non-blur theme, change to backdrop to none + if (!BlurEnabled) + { + backdropType = BackdropTypes.None; + } + + // Dropshadow on and control disabled.(user can't change dropshadow with blur theme) + if (BlurEnabled) + { + useDropShadowEffect = true; + } + + return (backdropType, useDropShadowEffect); + } + + private void SetBlurForWindow(string theme, BackdropTypes backdropType) + { + var dict = GetResourceDictionary(theme); + if (dict == null) return; + + var windowBorderStyle = dict.Contains("WindowBorderStyle") ? dict["WindowBorderStyle"] as Style : null; + if (windowBorderStyle == null) return; + + var mainWindow = Application.Current.MainWindow; + if (mainWindow == null) return; + + // Check if the theme supports blur + bool hasBlur = dict.Contains("ThemeBlurEnabled") && dict["ThemeBlurEnabled"] is bool b && b; + if (BlurEnabled && hasBlur && Win32Helper.IsBackdropSupported()) + { + // If the BackdropType is Mica or MicaAlt, set the windowborderstyle's background to transparent + if (backdropType == BackdropTypes.Mica || backdropType == BackdropTypes.MicaAlt) + { + windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType().FirstOrDefault(x => x.Property.Name == "Background")); + windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)))); + } + else if (backdropType == BackdropTypes.Acrylic) + { + windowBorderStyle.Setters.Remove(windowBorderStyle.Setters.OfType().FirstOrDefault(x => x.Property.Name == "Background")); + windowBorderStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(Colors.Transparent))); + } + + // Apply the blur effect + Win32Helper.DWMSetBackdropForWindow(mainWindow, backdropType); + ColorizeWindow(theme, backdropType); + } + else + { + // Apply default style when Blur is disabled + Win32Helper.DWMSetBackdropForWindow(mainWindow, BackdropTypes.None); + ColorizeWindow(theme, backdropType); + } + + UpdateResourceDictionary(dict); + } + + private void AutoDropShadow(bool useDropShadowEffect) + { + SetWindowCornerPreference("Default"); + RemoveDropShadowEffectFromCurrentTheme(); + if (useDropShadowEffect) + { + if (BlurEnabled && Win32Helper.IsBackdropSupported()) + { + SetWindowCornerPreference("Round"); + } + else + { + SetWindowCornerPreference("Default"); + AddDropShadowEffectToCurrentTheme(); + } + } + else + { + if (BlurEnabled && Win32Helper.IsBackdropSupported()) + { + SetWindowCornerPreference("Default"); + } + else + { + RemoveDropShadowEffectFromCurrentTheme(); + } + } + } + + private static void SetWindowCornerPreference(string cornerType) + { + Window mainWindow = Application.Current.MainWindow; + if (mainWindow == null) + return; + + Win32Helper.DWMSetCornerPreferenceForWindow(mainWindow, cornerType); + } + + // Get Background Color from WindowBorderStyle when there not color for BG. + // for theme has not "LightBG" or "DarkBG" case. + private Color GetWindowBorderStyleBackground(string theme) + { + var Resources = GetThemeResourceDictionary(theme); + var windowBorderStyle = (Style)Resources["WindowBorderStyle"]; + + var backgroundSetter = windowBorderStyle.Setters + .OfType() + .FirstOrDefault(s => s.Property == Border.BackgroundProperty); + + if (backgroundSetter != null) + { + // Background's Value is DynamicColor Case + var backgroundValue = backgroundSetter.Value; + + if (backgroundValue is SolidColorBrush solidColorBrush) + { + return solidColorBrush.Color; // Return SolidColorBrush's Color + } + else if (backgroundValue is DynamicResourceExtension dynamicResource) + { + // When DynamicResource Extension it is, Key is resource's name. + var resourceKey = backgroundSetter.Value.ToString(); + + // find key in resource and return color. + if (Resources.Contains(resourceKey)) + { + var colorResource = Resources[resourceKey]; + if (colorResource is SolidColorBrush colorBrush) + { + return colorBrush.Color; + } + else if (colorResource is Color color) + { + return color; + } + } + } + } + + return Colors.Transparent; // Default is transparent + } + + private void ApplyPreviewBackground(Color? bgColor = null) + { + if (bgColor == null) return; + + // Copy the existing WindowBorderStyle + var previewStyle = new Style(typeof(Border)); + if (Application.Current.Resources.Contains("WindowBorderStyle")) + { + if (Application.Current.Resources["WindowBorderStyle"] is Style originalStyle) + { + foreach (var setter in originalStyle.Setters.OfType()) + { + previewStyle.Setters.Add(new Setter(setter.Property, setter.Value)); + } + } + } + + // Apply background color (remove transparency in color) + // WPF does not allow the use of an acrylic brush within the window's internal area, + // so transparency effects are not applied to the preview. + Color backgroundColor = Color.FromRgb(bgColor.Value.R, bgColor.Value.G, bgColor.Value.B); + previewStyle.Setters.Add(new Setter(Border.BackgroundProperty, new SolidColorBrush(backgroundColor))); + + // The blur theme keeps the corner round fixed (applying DWM code to modify it causes rendering issues). + // The non-blur theme retains the previously set WindowBorderStyle. + if (BlurEnabled) + { + previewStyle.Setters.Add(new Setter(Border.CornerRadiusProperty, new CornerRadius(5))); + previewStyle.Setters.Add(new Setter(Border.BorderThicknessProperty, new Thickness(1))); + } + Application.Current.Resources["PreviewWindowBorderStyle"] = previewStyle; + } + + private void ColorizeWindow(string theme, BackdropTypes backdropType) + { + var dict = GetThemeResourceDictionary(theme); + if (dict == null) return; + + var mainWindow = Application.Current.MainWindow; + if (mainWindow == null) return; + + // Check if the theme supports blur + bool hasBlur = dict.Contains("ThemeBlurEnabled") && dict["ThemeBlurEnabled"] is bool b && b; + + // SystemBG value check (Auto, Light, Dark) + string systemBG = dict.Contains("SystemBG") ? dict["SystemBG"] as string : "Auto"; // 기본값 Auto + + // Check the user's ColorScheme setting + string colorScheme = _settings.ColorScheme; + + // Check system dark mode setting (read AppsUseLightTheme value) + int themeValue = (int)Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", "AppsUseLightTheme", 1); + bool isSystemDark = themeValue == 0; + + // Final decision on whether to use dark mode + bool useDarkMode = false; + + // If systemBG is not "Auto", prioritize it over ColorScheme and set the mode based on systemBG value + if (systemBG == "Dark") + { + useDarkMode = true; // Dark + } + else if (systemBG == "Light") + { + useDarkMode = false; // Light + } + else if (systemBG == "Auto") + { + // If systemBG is "Auto", decide based on ColorScheme + if (colorScheme == "Dark") + useDarkMode = true; + else if (colorScheme == "Light") + useDarkMode = false; + else + useDarkMode = isSystemDark; // Auto (based on system setting) + } + + // Apply DWM Dark Mode + Win32Helper.DWMSetDarkModeForWindow(mainWindow, useDarkMode); + + Color LightBG; + Color DarkBG; + + // Retrieve LightBG value (fallback to WindowBorderStyle background color if not found) + try + { + LightBG = dict.Contains("LightBG") ? (Color)dict["LightBG"] : GetWindowBorderStyleBackground(theme); + } + catch (Exception) + { + LightBG = GetWindowBorderStyleBackground(theme); + } + + // Retrieve DarkBG value (fallback to LightBG if not found) + try + { + DarkBG = dict.Contains("DarkBG") ? (Color)dict["DarkBG"] : LightBG; + } + catch (Exception) + { + DarkBG = LightBG; + } + + // Select background color based on ColorScheme and SystemBG + Color selectedBG = useDarkMode ? DarkBG : LightBG; + ApplyPreviewBackground(selectedBG); + + bool isBlurAvailable = hasBlur && Win32Helper.IsBackdropSupported(); // Windows 11 미만이면 hasBlur를 강제 false + + if (!isBlurAvailable) + { + mainWindow.Background = Brushes.Transparent; + } + else + { + // Only set the background to transparent if the theme supports blur + if (backdropType == BackdropTypes.Mica || backdropType == BackdropTypes.MicaAlt) + { + mainWindow.Background = new SolidColorBrush(Color.FromArgb(1, 0, 0, 0)); + } + else + { + mainWindow.Background = new SolidColorBrush(selectedBG); + } + } + } + + private static bool IsBlurTheme() + { + if (!Win32Helper.IsBackdropSupported()) // Windows 11 미만이면 무조건 false + return false; + + var resource = Application.Current.TryFindResource("ThemeBlurEnabled"); + + return resource is bool b && b; + } + + #endregion + + #region Classes + public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null); + + #endregion } } diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index c86ed4324..b4b2485c9 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -32,6 +32,7 @@ namespace Flow.Launcher.Infrastructure public static readonly string MissingImgIcon = Path.Combine(ImagesDirectory, "app_missing_img.png"); public static readonly string LoadingImgIcon = Path.Combine(ImagesDirectory, "loading.png"); public static readonly string ImageIcon = Path.Combine(ImagesDirectory, "image.png"); + public static readonly string HistoryIcon = Path.Combine(ImagesDirectory, "history.png"); public static string PythonPath; public static string NodePath; diff --git a/Flow.Launcher.Infrastructure/MonitorInfo.cs b/Flow.Launcher.Infrastructure/MonitorInfo.cs new file mode 100644 index 000000000..3221708c1 --- /dev/null +++ b/Flow.Launcher.Infrastructure/MonitorInfo.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using System; +using System.Runtime.InteropServices; +using System.Windows; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Graphics.Gdi; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace Flow.Launcher.Infrastructure; + +/// +/// Contains full information about a display monitor. +/// Codes are edited from: . +/// +internal class MonitorInfo +{ + /// + /// Gets the display monitors (including invisible pseudo-monitors associated with the mirroring drivers). + /// + /// A list of display monitors + public static unsafe IList GetDisplayMonitors() + { + var monitorCount = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CMONITORS); + var list = new List(monitorCount); + var callback = new MONITORENUMPROC((HMONITOR monitor, HDC deviceContext, RECT* rect, LPARAM data) => + { + list.Add(new MonitorInfo(monitor, rect)); + return true; + }); + var dwData = new LPARAM(); + var hdc = new HDC(); + bool ok = PInvoke.EnumDisplayMonitors(hdc, (RECT?)null, callback, dwData); + if (!ok) + { + Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error()); + } + return list; + } + + /// + /// Gets the display monitor that is nearest to a given window. + /// + /// Window handle + /// The display monitor that is nearest to a given window, or null if no monitor is found. + public static unsafe MonitorInfo GetNearestDisplayMonitor(HWND hwnd) + { + var nearestMonitor = PInvoke.MonitorFromWindow(hwnd, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST); + MonitorInfo nearestMonitorInfo = null; + var callback = new MONITORENUMPROC((HMONITOR monitor, HDC deviceContext, RECT* rect, LPARAM data) => + { + if (monitor == nearestMonitor) + { + nearestMonitorInfo = new MonitorInfo(monitor, rect); + return false; + } + return true; + }); + var dwData = new LPARAM(); + var hdc = new HDC(); + bool ok = PInvoke.EnumDisplayMonitors(hdc, (RECT?)null, callback, dwData); + if (!ok) + { + Marshal.ThrowExceptionForHR(Marshal.GetLastWin32Error()); + } + return nearestMonitorInfo; + } + + private readonly HMONITOR _monitor; + + internal unsafe MonitorInfo(HMONITOR monitor, RECT* rect) + { + RectMonitor = + new Rect(new Point(rect->left, rect->top), + new Point(rect->right, rect->bottom)); + _monitor = monitor; + var info = new MONITORINFOEXW() { monitorInfo = new MONITORINFO() { cbSize = (uint)sizeof(MONITORINFOEXW) } }; + GetMonitorInfo(monitor, ref info); + RectWork = + new Rect(new Point(info.monitorInfo.rcWork.left, info.monitorInfo.rcWork.top), + new Point(info.monitorInfo.rcWork.right, info.monitorInfo.rcWork.bottom)); + Name = new string(info.szDevice.AsSpan()).Replace("\0", "").Trim(); + } + + /// + /// Gets the name of the display. + /// + public string Name { get; } + + /// + /// Gets the display monitor rectangle, expressed in virtual-screen coordinates. + /// + /// + /// If the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values. + /// + public Rect RectMonitor { get; } + + /// + /// Gets the work area rectangle of the display monitor, expressed in virtual-screen coordinates. + /// + /// + /// If the monitor is not the primary display monitor, some of the rectangle's coordinates may be negative values. + /// + public Rect RectWork { get; } + + /// + /// Gets if the monitor is the the primary display monitor. + /// + public bool IsPrimary => _monitor == PInvoke.MonitorFromWindow(new(IntPtr.Zero), MONITOR_FROM_FLAGS.MONITOR_DEFAULTTOPRIMARY); + + /// + public override string ToString() => $"{Name} {RectMonitor.Width}x{RectMonitor.Height}"; + + private static unsafe bool GetMonitorInfo(HMONITOR hMonitor, ref MONITORINFOEXW lpmi) + { + fixed (MONITORINFOEXW* lpmiLocal = &lpmi) + { + var lpmiBase = (MONITORINFO*)lpmiLocal; + var __result = PInvoke.GetMonitorInfo(hMonitor, lpmiBase); + return __result; + } + } +} diff --git a/Flow.Launcher.Infrastructure/NativeMethods.txt b/Flow.Launcher.Infrastructure/NativeMethods.txt index f117534a1..363ecb9d0 100644 --- a/Flow.Launcher.Infrastructure/NativeMethods.txt +++ b/Flow.Launcher.Infrastructure/NativeMethods.txt @@ -16,4 +16,46 @@ WM_KEYUP WM_SYSKEYDOWN WM_SYSKEYUP -EnumWindows \ No newline at end of file +EnumWindows + +DwmSetWindowAttribute +DWM_SYSTEMBACKDROP_TYPE +DWM_WINDOW_CORNER_PREFERENCE + +MAX_PATH +SystemParametersInfo + +SetForegroundWindow + +GetWindowLong +GetForegroundWindow +GetDesktopWindow +GetShellWindow +GetWindowRect +GetClassName +FindWindowEx +WINDOW_STYLE + +SetLastError +WINDOW_EX_STYLE + +GetSystemMetrics +EnumDisplayMonitors +MonitorFromWindow +GetMonitorInfo +MONITORINFOEXW + +WM_ENTERSIZEMOVE +WM_EXITSIZEMOVE + +GetKeyboardLayout +GetWindowThreadProcessId +ActivateKeyboardLayout +GetKeyboardLayoutList +PostMessage +WM_INPUTLANGCHANGEREQUEST +INPUTLANGCHANGE_FORWARD +LOCALE_TRANSIENT_KEYBOARD1 +LOCALE_TRANSIENT_KEYBOARD2 +LOCALE_TRANSIENT_KEYBOARD3 +LOCALE_TRANSIENT_KEYBOARD4 \ No newline at end of file diff --git a/Flow.Launcher.Infrastructure/PInvokeExtensions.cs b/Flow.Launcher.Infrastructure/PInvokeExtensions.cs new file mode 100644 index 000000000..1a72ab7a6 --- /dev/null +++ b/Flow.Launcher.Infrastructure/PInvokeExtensions.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; +using Windows.Win32.Foundation; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace Windows.Win32; + +// Edited from: https://github.com/files-community/Files +internal static partial class PInvoke +{ + [DllImport("User32", EntryPoint = "SetWindowLongW", ExactSpelling = true)] + static extern int _SetWindowLong(HWND hWnd, int nIndex, int dwNewLong); + + [DllImport("User32", EntryPoint = "SetWindowLongPtrW", ExactSpelling = true)] + static extern nint _SetWindowLongPtr(HWND hWnd, int nIndex, nint dwNewLong); + + // NOTE: + // CsWin32 doesn't generate SetWindowLong on other than x86 and vice versa. + // For more info, visit https://github.com/microsoft/CsWin32/issues/882 + public static unsafe nint SetWindowLongPtr(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, nint dwNewLong) + { + return sizeof(nint) is 4 + ? _SetWindowLong(hWnd, (int)nIndex, (int)dwNewLong) + : _SetWindowLongPtr(hWnd, (int)nIndex, dwNewLong); + } +} diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 93f6db111..63debfb47 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -76,6 +76,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings } } public bool UseDropShadowEffect { get; set; } = true; + public BackdropTypes BackdropType{ get; set; } = BackdropTypes.None; /* Appearance Settings. It should be separated from the setting later.*/ public double WindowHeightSize { get; set; } = 42; @@ -430,4 +431,12 @@ namespace Flow.Launcher.Infrastructure.UserSettings Fast, Custom } + + public enum BackdropTypes + { + None, + Acrylic, + Mica, + MicaAlt + } } diff --git a/Flow.Launcher.Infrastructure/Win32Helper.cs b/Flow.Launcher.Infrastructure/Win32Helper.cs index 867fef4f5..7a3a0c36e 100644 --- a/Flow.Launcher.Infrastructure/Win32Helper.cs +++ b/Flow.Launcher.Infrastructure/Win32Helper.cs @@ -1,7 +1,18 @@ using System; +using System.ComponentModel; +using System.Globalization; using System.Runtime.InteropServices; -using System.Windows.Interop; using System.Windows; +using System.Windows.Interop; +using System.Windows.Media; +using Flow.Launcher.Infrastructure.UserSettings; +using Microsoft.Win32; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.Graphics.Dwm; +using Windows.Win32.UI.Input.KeyboardAndMouse; +using Windows.Win32.UI.WindowsAndMessaging; +using Point = System.Windows.Point; namespace Flow.Launcher.Infrastructure { @@ -9,93 +20,473 @@ namespace Flow.Launcher.Infrastructure { #region Blur Handling - /* - Found on https://github.com/riverar/sample-win10-aeroglass - */ - - private enum AccentState + public static bool IsBackdropSupported() { - ACCENT_DISABLED = 0, - ACCENT_ENABLE_GRADIENT = 1, - ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, - ACCENT_ENABLE_BLURBEHIND = 3, - ACCENT_INVALID_STATE = 4 + // Mica and Acrylic only supported Windows 11 22000+ + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && + Environment.OSVersion.Version.Build >= 22000; } - [StructLayout(LayoutKind.Sequential)] - private struct AccentPolicy + public static unsafe bool DWMSetCloakForWindow(Window window, bool cloak) { - public AccentState AccentState; - public int AccentFlags; - public int GradientColor; - public int AnimationId; + var cloaked = cloak ? 1 : 0; + + return PInvoke.DwmSetWindowAttribute( + GetWindowHandle(window), + DWMWINDOWATTRIBUTE.DWMWA_CLOAK, + &cloaked, + (uint)Marshal.SizeOf()).Succeeded; } - [StructLayout(LayoutKind.Sequential)] - private struct WindowCompositionAttributeData + public static unsafe bool DWMSetBackdropForWindow(Window window, BackdropTypes backdrop) { - public WindowCompositionAttribute Attribute; - public IntPtr Data; - public int SizeOfData; + var backdropType = backdrop switch + { + BackdropTypes.Acrylic => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_TRANSIENTWINDOW, + BackdropTypes.Mica => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_MAINWINDOW, + BackdropTypes.MicaAlt => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_TABBEDWINDOW, + _ => DWM_SYSTEMBACKDROP_TYPE.DWMSBT_AUTO + }; + + return PInvoke.DwmSetWindowAttribute( + GetWindowHandle(window), + DWMWINDOWATTRIBUTE.DWMWA_SYSTEMBACKDROP_TYPE, + &backdropType, + (uint)Marshal.SizeOf()).Succeeded; } - private enum WindowCompositionAttribute + public static unsafe bool DWMSetDarkModeForWindow(Window window, bool useDarkMode) { - WCA_ACCENT_POLICY = 19 - } + var darkMode = useDarkMode ? 1 : 0; - [DllImport("user32.dll")] - private static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data); + return PInvoke.DwmSetWindowAttribute( + GetWindowHandle(window), + DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, + &darkMode, + (uint)Marshal.SizeOf()).Succeeded; + } /// - /// Checks if the blur theme is enabled + /// /// - public static bool IsBlurTheme() + /// + /// DoNotRound, Round, RoundSmall, Default + /// + public static unsafe bool DWMSetCornerPreferenceForWindow(Window window, string cornerType) { - if (Environment.OSVersion.Version >= new Version(6, 2)) + var preference = cornerType switch { - var resource = Application.Current.TryFindResource("ThemeBlurEnabled"); + "DoNotRound" => DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_DONOTROUND, + "Round" => DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND, + "RoundSmall" => DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUNDSMALL, + "Default" => DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_DEFAULT, + _ => throw new InvalidOperationException("Invalid corner type") + }; - if (resource is bool b) - return b; + return PInvoke.DwmSetWindowAttribute( + GetWindowHandle(window), + DWMWINDOWATTRIBUTE.DWMWA_WINDOW_CORNER_PREFERENCE, + &preference, + (uint)Marshal.SizeOf()).Succeeded; + } + #endregion + + #region Wallpaper + + public static unsafe string GetWallpaperPath() + { + var wallpaperPtr = stackalloc char[(int)PInvoke.MAX_PATH]; + PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETDESKWALLPAPER, PInvoke.MAX_PATH, + wallpaperPtr, + 0); + var wallpaper = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(wallpaperPtr); + + return wallpaper.ToString(); + } + + #endregion + + #region Window Foreground + + public static nint GetForegroundWindow() + { + return PInvoke.GetForegroundWindow().Value; + } + + public static bool SetForegroundWindow(Window window) + { + return PInvoke.SetForegroundWindow(GetWindowHandle(window)); + } + + public static bool SetForegroundWindow(nint handle) + { + return PInvoke.SetForegroundWindow(new(handle)); + } + + #endregion + + #region Task Switching + + /// + /// Hide windows in the Alt+Tab window list + /// + /// To hide a window + public static void HideFromAltTab(Window window) + { + var hwnd = GetWindowHandle(window); + + var exStyle = GetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); + + // Add TOOLWINDOW style, remove APPWINDOW style + var newExStyle = ((uint)exStyle | (uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) & ~(uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW; + + SetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle); + } + + /// + /// Restore window display in the Alt+Tab window list. + /// + /// To restore the displayed window + public static void ShowInAltTab(Window window) + { + var hwnd = GetWindowHandle(window); + + var exStyle = GetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); + + // Remove the TOOLWINDOW style and add the APPWINDOW style. + var newExStyle = ((uint)exStyle & ~(uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) | (uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW; + + SetWindowStyle(GetWindowHandle(window), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle); + } + + /// + /// Disable windows toolbar's control box + /// This will also disable system menu with Alt+Space hotkey + /// + public static void DisableControlBox(Window window) + { + var hwnd = GetWindowHandle(window); + + var style = GetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE); + + style &= ~(int)WINDOW_STYLE.WS_SYSMENU; + + SetWindowStyle(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, style); + } + + private static int GetWindowStyle(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex) + { + var style = PInvoke.GetWindowLong(hWnd, nIndex); + if (style == 0 && Marshal.GetLastPInvokeError() != 0) + { + throw new Win32Exception(Marshal.GetLastPInvokeError()); + } + return style; + } + + private static nint SetWindowStyle(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, int dwNewLong) + { + PInvoke.SetLastError(WIN32_ERROR.NO_ERROR); // Clear any existing error + + var result = PInvoke.SetWindowLongPtr(hWnd, nIndex, dwNewLong); + if (result == 0 && Marshal.GetLastPInvokeError() != 0) + { + throw new Win32Exception(Marshal.GetLastPInvokeError()); + } + + return result; + } + + #endregion + + #region Window Fullscreen + + private const string WINDOW_CLASS_CONSOLE = "ConsoleWindowClass"; + private const string WINDOW_CLASS_WINTAB = "Flip3D"; + private const string WINDOW_CLASS_PROGMAN = "Progman"; + private const string WINDOW_CLASS_WORKERW = "WorkerW"; + + private static HWND _hwnd_shell; + private static HWND HWND_SHELL => + _hwnd_shell != HWND.Null ? _hwnd_shell : _hwnd_shell = PInvoke.GetShellWindow(); + + private static HWND _hwnd_desktop; + private static HWND HWND_DESKTOP => + _hwnd_desktop != HWND.Null ? _hwnd_desktop : _hwnd_desktop = PInvoke.GetDesktopWindow(); + + public static unsafe bool IsForegroundWindowFullscreen() + { + // Get current active window + var hWnd = PInvoke.GetForegroundWindow(); + if (hWnd.Equals(HWND.Null)) + { return false; } - return false; + // If current active window is desktop or shell, exit early + if (hWnd.Equals(HWND_DESKTOP) || hWnd.Equals(HWND_SHELL)) + { + return false; + } + + string windowClass; + const int capacity = 256; + Span buffer = stackalloc char[capacity]; + int validLength; + fixed (char* pBuffer = buffer) + { + validLength = PInvoke.GetClassName(hWnd, pBuffer, capacity); + } + + windowClass = buffer[..validLength].ToString(); + + // For Win+Tab (Flip3D) + if (windowClass == WINDOW_CLASS_WINTAB) + { + return false; + } + + PInvoke.GetWindowRect(hWnd, out var appBounds); + + // For console (ConsoleWindowClass), we have to check for negative dimensions + if (windowClass == WINDOW_CLASS_CONSOLE) + { + return appBounds.top < 0 && appBounds.bottom < 0; + } + + // For desktop (Progman or WorkerW, depends on the system), we have to check + if (windowClass is WINDOW_CLASS_PROGMAN or WINDOW_CLASS_WORKERW) + { + var hWndDesktop = PInvoke.FindWindowEx(hWnd, HWND.Null, "SHELLDLL_DefView", null); + hWndDesktop = PInvoke.FindWindowEx(hWndDesktop, HWND.Null, "SysListView32", "FolderView"); + if (hWndDesktop.Value != IntPtr.Zero) + { + return false; + } + } + + var monitorInfo = MonitorInfo.GetNearestDisplayMonitor(hWnd); + return (appBounds.bottom - appBounds.top) == monitorInfo.RectMonitor.Height && + (appBounds.right - appBounds.left) == monitorInfo.RectMonitor.Width; + } + + #endregion + + #region Pixel to DIP + + /// + /// Transforms pixels to Device Independent Pixels used by WPF + /// + /// current window, required to get presentation source + /// horizontal position in pixels + /// vertical position in pixels + /// point containing device independent pixels + public static Point TransformPixelsToDIP(Visual visual, double unitX, double unitY) + { + Matrix matrix; + var source = PresentationSource.FromVisual(visual); + if (source is not null) + { + matrix = source.CompositionTarget.TransformFromDevice; + } + else + { + using var src = new HwndSource(new HwndSourceParameters()); + matrix = src.CompositionTarget.TransformFromDevice; + } + + return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY)); + } + + #endregion + + #region WndProc + + public const int WM_ENTERSIZEMOVE = (int)PInvoke.WM_ENTERSIZEMOVE; + public const int WM_EXITSIZEMOVE = (int)PInvoke.WM_EXITSIZEMOVE; + + #endregion + + #region Window Handle + + internal static HWND GetWindowHandle(Window window, bool ensure = false) + { + var windowHelper = new WindowInteropHelper(window); + if (ensure) + { + windowHelper.EnsureHandle(); + } + return new(windowHelper.Handle); + } + + #endregion + + #region Keyboard Layout + + private const string UserProfileRegistryPath = @"Control Panel\International\User Profile"; + + // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f + private const string EnglishLanguageTag = "en"; + + private static readonly string[] ImeLanguageTags = + { + "zh", // Chinese + "ja", // Japanese + "ko", // Korean + }; + + private const uint KeyboardLayoutLoWord = 0xFFFF; + + // Store the previous keyboard layout + private static HKL _previousLayout; + + /// + /// Switches the keyboard layout to English if available. + /// + /// If true, the current keyboard layout will be stored for later restoration. + /// Thrown when there's an error getting the window thread process ID. + public static unsafe void SwitchToEnglishKeyboardLayout(bool backupPrevious) + { + // Find an installed English layout + var enHKL = FindEnglishKeyboardLayout(); + + // No installed English layout found + if (enHKL == HKL.Null) return; + + // Get the current foreground window + var hwnd = PInvoke.GetForegroundWindow(); + if (hwnd == HWND.Null) return; + + // Get the current foreground window thread ID + var threadId = PInvoke.GetWindowThreadProcessId(hwnd); + if (threadId == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); + + // If the current layout has an IME mode, disable it without switching to another layout. + // This is needed because for languages with IME mode, Flow Launcher just temporarily disables + // the IME mode instead of switching to another layout. + var currentLayout = PInvoke.GetKeyboardLayout(threadId); + var currentLangId = (uint)currentLayout.Value & KeyboardLayoutLoWord; + foreach (var langTag in ImeLanguageTags) + { + if (GetLanguageTag(currentLangId).StartsWith(langTag, StringComparison.OrdinalIgnoreCase)) + { + return; + } + } + + // Backup current keyboard layout + if (backupPrevious) _previousLayout = currentLayout; + + // Switch to English layout + PInvoke.ActivateKeyboardLayout(enHKL, 0); } /// - /// Sets the blur for a window via SetWindowCompositionAttribute + /// Restores the previously backed-up keyboard layout. + /// If it wasn't backed up or has already been restored, this method does nothing. /// - public static void SetBlurForWindow(Window w, bool blur) + public static void RestorePreviousKeyboardLayout() { - SetWindowAccent(w, blur ? AccentState.ACCENT_ENABLE_BLURBEHIND : AccentState.ACCENT_DISABLED); + if (_previousLayout == HKL.Null) return; + + var hwnd = PInvoke.GetForegroundWindow(); + if (hwnd == HWND.Null) return; + + PInvoke.PostMessage( + hwnd, + PInvoke.WM_INPUTLANGCHANGEREQUEST, + PInvoke.INPUTLANGCHANGE_FORWARD, + _previousLayout.Value + ); + + _previousLayout = HKL.Null; } - private static void SetWindowAccent(Window w, AccentState state) + /// + /// Finds an installed English keyboard layout. + /// + /// + /// + private static unsafe HKL FindEnglishKeyboardLayout() { - var windowHelper = new WindowInteropHelper(w); + // Get the number of keyboard layouts + int count = PInvoke.GetKeyboardLayoutList(0, null); + if (count <= 0) return HKL.Null; - windowHelper.EnsureHandle(); - - var accent = new AccentPolicy { AccentState = state }; - var accentStructSize = Marshal.SizeOf(accent); - - var accentPtr = Marshal.AllocHGlobal(accentStructSize); - Marshal.StructureToPtr(accent, accentPtr, false); - - var data = new WindowCompositionAttributeData + // Get all keyboard layouts + var handles = new HKL[count]; + fixed (HKL* h = handles) { - Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY, - SizeOfData = accentStructSize, - Data = accentPtr - }; + var result = PInvoke.GetKeyboardLayoutList(count, h); + if (result == 0) throw new Win32Exception(Marshal.GetLastWin32Error()); + } - SetWindowCompositionAttribute(windowHelper.Handle, ref data); + // Look for any English keyboard layout + foreach (var hkl in handles) + { + // The lower word contains the language identifier + var langId = (uint)hkl.Value & KeyboardLayoutLoWord; + var langTag = GetLanguageTag(langId); - Marshal.FreeHGlobal(accentPtr); + // Check if it's an English layout + if (langTag.StartsWith(EnglishLanguageTag, StringComparison.OrdinalIgnoreCase)) + { + return hkl; + } + } + + return HKL.Null; } + + /// + /// Returns the + /// + /// BCP 47 language tag + /// + /// of the current input language. + /// + /// + /// Edited from: https://github.com/dotnet/winforms + /// + private static string GetLanguageTag(uint langId) + { + // We need to convert the language identifier to a language tag, because they are deprecated and may have a + // transient value. + // https://learn.microsoft.com/globalization/locale/other-locale-names#lcid + // https://learn.microsoft.com/windows/win32/winmsg/wm-inputlangchange#remarks + // + // It turns out that the LCIDToLocaleName API, which is used inside CultureInfo, may return incorrect + // language tags for transient language identifiers. For example, it returns "nqo-GN" and "jv-Java-ID" + // instead of the "nqo" and "jv-Java" (as seen in the Get-WinUserLanguageList PowerShell cmdlet). + // + // Try to extract proper language tag from registry as a workaround approved by a Windows team. + // https://github.com/dotnet/winforms/pull/8573#issuecomment-1542600949 + // + // NOTE: this logic may break in future versions of Windows since it is not documented. + if (langId is PInvoke.LOCALE_TRANSIENT_KEYBOARD1 + or PInvoke.LOCALE_TRANSIENT_KEYBOARD2 + or PInvoke.LOCALE_TRANSIENT_KEYBOARD3 + or PInvoke.LOCALE_TRANSIENT_KEYBOARD4) + { + using var key = Registry.CurrentUser.OpenSubKey(UserProfileRegistryPath); + if (key?.GetValue("Languages") is string[] languages) + { + foreach (string language in languages) + { + using var subKey = key.OpenSubKey(language); + if (subKey?.GetValue("TransientLangId") is int transientLangId + && transientLangId == langId) + { + return language; + } + } + } + } + + return CultureInfo.GetCultureInfo((int)langId).Name; + } + #endregion } } diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 2feb21b12..1472813b8 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -66,7 +66,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs index 4c7af4cd4..f178ebb90 100644 --- a/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs +++ b/Flow.Launcher.Plugin/Interfaces/IPublicAPI.cs @@ -152,7 +152,6 @@ namespace Flow.Launcher.Plugin /// public void RemoveGlobalKeyboardCallback(Func callback); - /// /// Fuzzy Search the string with the given query. This is the core search mechanism Flow uses /// @@ -191,14 +190,15 @@ namespace Flow.Launcher.Plugin Task HttpDownloadAsync([NotNull] string url, [NotNull] string filePath, Action reportProgress = null, CancellationToken token = default); /// - /// Add ActionKeyword for specific plugin + /// Add ActionKeyword and update action keyword metadata for specific plugin + /// Before adding, please check if action keyword is already assigned by /// /// ID for plugin that needs to add action keyword /// The actionkeyword that is supposed to be added void AddActionKeyword(string pluginId, string newActionKeyword); /// - /// Remove ActionKeyword for specific plugin + /// Remove ActionKeyword and update action keyword metadata for specific plugin /// /// ID for plugin that needs to remove action keyword /// The actionkeyword that is supposed to be removed diff --git a/Flow.Launcher.Plugin/PluginMetadata.cs b/Flow.Launcher.Plugin/PluginMetadata.cs index b4e06913e..91256298b 100644 --- a/Flow.Launcher.Plugin/PluginMetadata.cs +++ b/Flow.Launcher.Plugin/PluginMetadata.cs @@ -34,6 +34,8 @@ namespace Flow.Launcher.Plugin public List ActionKeywords { get; set; } + public bool HideActionKeywordPanel { get; set; } + public string IcoPath { get; set;} public override string ToString() diff --git a/Flow.Launcher.Plugin/Query.cs b/Flow.Launcher.Plugin/Query.cs index e182491c2..15b2dd171 100644 --- a/Flow.Launcher.Plugin/Query.cs +++ b/Flow.Launcher.Plugin/Query.cs @@ -39,10 +39,9 @@ namespace Flow.Launcher.Plugin public const string TermSeparator = " "; /// - /// User can set multiple action keywords seperated by ';' + /// User can set multiple action keywords seperated by whitespace /// - public const string ActionKeywordSeparator = ";"; - + public const string ActionKeywordSeparator = TermSeparator; /// /// Wildcard action keyword. Plugins using this value will be queried on every search. diff --git a/Flow.Launcher/ActionKeywords.xaml.cs b/Flow.Launcher/ActionKeywords.xaml.cs index c3966e618..d403d4847 100644 --- a/Flow.Launcher/ActionKeywords.xaml.cs +++ b/Flow.Launcher/ActionKeywords.xaml.cs @@ -3,6 +3,8 @@ using Flow.Launcher.Core.Resource; using Flow.Launcher.Plugin; using Flow.Launcher.ViewModel; using Flow.Launcher.Core; +using System.Linq; +using System.Collections.Generic; namespace Flow.Launcher { @@ -32,14 +34,37 @@ namespace Flow.Launcher private void btnDone_OnClick(object sender, RoutedEventArgs _) { - var oldActionKeyword = plugin.Metadata.ActionKeywords[0]; - var newActionKeyword = tbAction.Text.Trim(); - newActionKeyword = newActionKeyword.Length > 0 ? newActionKeyword : "*"; - - if (!PluginViewModel.IsActionKeywordRegistered(newActionKeyword)) + var oldActionKeywords = plugin.Metadata.ActionKeywords; + + var newActionKeywords = tbAction.Text.Split(Query.ActionKeywordSeparator).ToList(); + newActionKeywords.RemoveAll(string.IsNullOrEmpty); + newActionKeywords = newActionKeywords.Distinct().ToList(); + + newActionKeywords = newActionKeywords.Count > 0 ? newActionKeywords : new() { Query.GlobalPluginWildcardSign }; + + var addedActionKeywords = newActionKeywords.Except(oldActionKeywords).ToList(); + var removedActionKeywords = oldActionKeywords.Except(newActionKeywords).ToList(); + if (!addedActionKeywords.Any(App.API.ActionKeywordAssigned)) { - pluginViewModel.ChangeActionKeyword(newActionKeyword, oldActionKeyword); - Close(); + if (oldActionKeywords.Count != newActionKeywords.Count) + { + ReplaceActionKeyword(plugin.Metadata.ID, removedActionKeywords, addedActionKeywords); + return; + } + + var sortedOldActionKeywords = oldActionKeywords.OrderBy(s => s).ToList(); + var sortedNewActionKeywords = newActionKeywords.OrderBy(s => s).ToList(); + + if (sortedOldActionKeywords.SequenceEqual(sortedNewActionKeywords)) + { + // User just changes the sequence of action keywords + var msg = translater.GetTranslation("newActionKeywordsSameAsOld"); + MessageBoxEx.Show(msg); + } + else + { + ReplaceActionKeyword(plugin.Metadata.ID, removedActionKeywords, addedActionKeywords); + } } else { @@ -47,5 +72,21 @@ namespace Flow.Launcher App.API.ShowMsgBox(msg); } } + + private void ReplaceActionKeyword(string id, IReadOnlyList removedActionKeywords, IReadOnlyList addedActionKeywords) + { + foreach (var actionKeyword in removedActionKeywords) + { + App.API.RemoveActionKeyword(id, actionKeyword); + } + foreach (var actionKeyword in addedActionKeywords) + { + App.API.AddActionKeyword(id, actionKeyword); + } + + // Update action keywords text and close window + pluginViewModel.OnActionKeywordsChanged(); + Close(); + } } } diff --git a/Flow.Launcher/App.xaml b/Flow.Launcher/App.xaml index 17c0ae0d5..565bbe3c7 100644 --- a/Flow.Launcher/App.xaml +++ b/Flow.Launcher/App.xaml @@ -4,7 +4,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="http://schemas.modernwpf.com/2019" ShutdownMode="OnMainWindowClose" - Startup="OnStartupAsync"> + Startup="OnStartup"> diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index ab68cf426..833c63ddf 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -27,11 +27,26 @@ namespace Flow.Launcher { public partial class App : IDisposable, ISingleInstanceApp { + #region Public Properties + public static IPublicAPI API { get; private set; } - private const string Unique = "Flow.Launcher_Unique_Application_Mutex"; + + #endregion + + #region Private Fields + private static bool _disposed; + private MainWindow _mainWindow; + private readonly MainViewModel _mainVM; private readonly Settings _settings; + // To prevent two disposals running at the same time. + private static readonly object _disposingLock = new(); + + #endregion + + #region Constructor + public App() { // Initialize settings @@ -64,6 +79,7 @@ namespace Flow.Launcher .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() ).Build(); Ioc.Default.ConfigureServices(host.Services); } @@ -78,37 +94,47 @@ namespace Flow.Launcher { API = Ioc.Default.GetRequiredService(); _settings.Initialize(); + _mainVM = Ioc.Default.GetRequiredService(); } catch (Exception e) { ShowErrorMsgBoxAndFailFast("Cannot initialize api and settings, please open new issue in Flow.Launcher", e); return; } + + // Local function + static void ShowErrorMsgBoxAndFailFast(string message, Exception e) + { + // Firstly show users the message + MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error); + + // Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info. + Environment.FailFast(message, e); + } } - private static void ShowErrorMsgBoxAndFailFast(string message, Exception e) - { - // Firstly show users the message - MessageBox.Show(e.ToString(), message, MessageBoxButton.OK, MessageBoxImage.Error); + #endregion - // Flow cannot construct its App instance, so ensure Flow crashes w/ the exception info. - Environment.FailFast(message, e); - } + #region Main [STAThread] public static void Main() { - if (SingleInstance.InitializeAsFirstInstance(Unique)) + if (SingleInstance.InitializeAsFirstInstance()) { - using (var application = new App()) - { - application.InitializeComponent(); - application.Run(); - } + using var application = new App(); + application.InitializeComponent(); + application.Run(); } } - private async void OnStartupAsync(object sender, StartupEventArgs e) + #endregion + + #region App Events + +#pragma warning disable VSTHRD100 // Avoid async void methods + + private async void OnStartup(object sender, StartupEventArgs e) { await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () => { @@ -126,29 +152,33 @@ namespace Flow.Launcher AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings); - // TODO: Clean InternationalizationManager.Instance and InternationalizationManager.Instance.GetTranslation in future - InternationalizationManager.Instance.ChangeLanguage(_settings.Language); - PluginManager.LoadPlugins(_settings.PluginSettings); + // Register ResultsUpdated event after all plugins are loaded + Ioc.Default.GetRequiredService().RegisterResultsUpdatedEvent(); + Http.Proxy = _settings.Proxy; await PluginManager.InitializePluginsAsync(); + + // Change language after all plugins are initialized because we need to update plugin title based on their api + // TODO: Clean InternationalizationManager.Instance and InternationalizationManager.Instance.GetTranslation in future + await Ioc.Default.GetRequiredService().InitializeLanguageAsync(); + await imageLoadertask; - var mainVM = Ioc.Default.GetRequiredService(); - var window = new MainWindow(_settings, mainVM); + _mainWindow = new MainWindow(); Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}"); - Current.MainWindow = window; + Current.MainWindow = _mainWindow; Current.MainWindow.Title = Constant.FlowLauncher; HotKeyMapper.Initialize(); // main windows needs initialized before theme change because of blur settings // TODO: Clean ThemeManager.Instance in future - ThemeManager.Instance.ChangeTheme(_settings.Theme); + Ioc.Default.GetRequiredService().ChangeTheme(); Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); @@ -158,11 +188,12 @@ namespace Flow.Launcher AutoUpdates(); API.SaveAppAllSettings(); - Log.Info( - "|App.OnStartup|End Flow Launcher startup ---------------------------------------------------- "); + Log.Info("|App.OnStartup|End Flow Launcher startup ----------------------------------------------------"); }); } +#pragma warning restore VSTHRD100 // Avoid async void methods + private void AutoStartup() { // we try to enable auto-startup on first launch, or reenable if it was removed @@ -185,13 +216,11 @@ namespace Flow.Launcher // 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); + API.ShowMsg(API.GetTranslation("setAutoStartFailed"), e.Message); } } } - //[Conditional("RELEASE")] private void AutoUpdates() { _ = Task.Run(async () => @@ -209,11 +238,29 @@ namespace Flow.Launcher }); } + #endregion + + #region Register Events + private void RegisterExitEvents() { - AppDomain.CurrentDomain.ProcessExit += (s, e) => Dispose(); - Current.Exit += (s, e) => Dispose(); - Current.SessionEnding += (s, e) => Dispose(); + AppDomain.CurrentDomain.ProcessExit += (s, e) => + { + Log.Info("|App.RegisterExitEvents|Process Exit"); + Dispose(); + }; + + Current.Exit += (s, e) => + { + Log.Info("|App.RegisterExitEvents|Application Exit"); + Dispose(); + }; + + Current.SessionEnding += (s, e) => + { + Log.Info("|App.RegisterExitEvents|Session Ending"); + Dispose(); + }; } /// @@ -234,20 +281,60 @@ namespace Flow.Launcher AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle; } - public void Dispose() + #endregion + + #region IDisposable + + protected virtual void Dispose(bool disposing) { - // if sessionending is called, exit proverbially be called when log off / shutdown - // but if sessionending is not called, exit won't be called when log off / shutdown - if (!_disposed) + // Prevent two disposes at the same time. + lock (_disposingLock) { - API.SaveAppAllSettings(); + if (!disposing) + { + return; + } + + if (_disposed) + { + return; + } + _disposed = true; } + + Stopwatch.Normal("|App.Dispose|Dispose cost", () => + { + Log.Info("|App.Dispose|Begin Flow Launcher dispose ----------------------------------------------------"); + + if (disposing) + { + // Dispose needs to be called on the main Windows thread, + // since some resources owned by the thread need to be disposed. + _mainWindow?.Dispatcher.Invoke(_mainWindow.Dispose); + _mainVM?.Dispose(); + } + + Log.Info("|App.Dispose|End Flow Launcher dispose ----------------------------------------------------"); + }); } + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + #endregion + + #region ISingleInstanceApp + public void OnSecondAppStarted() { Ioc.Default.GetRequiredService().Show(); } + + #endregion } } diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml b/Flow.Launcher/CustomQueryHotkeySetting.xaml index 70ebb404b..0171e6d79 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml @@ -58,9 +58,9 @@ - + - + @@ -99,12 +99,12 @@ Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" - Margin="10,0,10,0" + Margin="10 0 10 0" HorizontalAlignment="Left" VerticalAlignment="Center" HorizontalContentAlignment="Left" - HotkeySettings="{Binding Settings}" - DefaultHotkey="" /> + DefaultHotkey="" + Type="CustomQueryHotkey" /> @@ -133,21 +133,21 @@ + BorderThickness="0 1 0 0"> /// True if this is the first instance of the application. - public static bool InitializeAsFirstInstance( string uniqueName ) + public static bool InitializeAsFirstInstance() { // Build unique application Id and the IPC channel name. - string applicationIdentifier = uniqueName + Environment.UserName; + string applicationIdentifier = InstanceMutexName + Environment.UserName; - string channelName = String.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix); + string channelName = string.Concat(applicationIdentifier, Delimiter, ChannelNameSuffix); // Create mutex based on unique application Id to check if this is the first instance of the application. - bool firstInstance; - singleInstanceMutex = new Mutex(true, applicationIdentifier, out firstInstance); + SingleInstanceMutex = new Mutex(true, applicationIdentifier, out var firstInstance); if (firstInstance) { - _ = CreateRemoteService(channelName); + _ = CreateRemoteServiceAsync(channelName); return true; } else { - _ = SignalFirstInstance(channelName); + _ = SignalFirstInstanceAsync(channelName); return false; } } @@ -81,7 +79,7 @@ namespace Flow.Launcher.Helper /// public static void Cleanup() { - singleInstanceMutex?.ReleaseMutex(); + SingleInstanceMutex?.ReleaseMutex(); } #endregion @@ -93,22 +91,19 @@ namespace Flow.Launcher.Helper /// Once receives signal from client, will activate first instance. /// /// Application's IPC channel name. - private static async Task CreateRemoteService(string channelName) + private static async Task CreateRemoteServiceAsync(string channelName) { - using (NamedPipeServerStream pipeServer = new NamedPipeServerStream(channelName, PipeDirection.In)) + using NamedPipeServerStream pipeServer = new NamedPipeServerStream(channelName, PipeDirection.In); + while (true) { - while(true) - { - // Wait for connection to the pipe - await pipeServer.WaitForConnectionAsync(); - if (Application.Current != null) - { - // Do an asynchronous call to ActivateFirstInstance function - Application.Current.Dispatcher.Invoke(ActivateFirstInstance); - } - // Disconect client - pipeServer.Disconnect(); - } + // Wait for connection to the pipe + await pipeServer.WaitForConnectionAsync(); + + // Do an asynchronous call to ActivateFirstInstance function + Application.Current?.Dispatcher.Invoke(ActivateFirstInstance); + + // Disconect client + pipeServer.Disconnect(); } } @@ -119,25 +114,13 @@ namespace Flow.Launcher.Helper /// /// Command line arguments for the second instance, passed to the first instance to take appropriate action. /// - private static async Task SignalFirstInstance(string channelName) + private static async Task SignalFirstInstanceAsync(string channelName) { // Create a client pipe connected to server - using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", channelName, PipeDirection.Out)) - { - // Connect to the available pipe - await pipeClient.ConnectAsync(0); - } - } + using NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", channelName, PipeDirection.Out); - /// - /// Callback for activating first instance of the application. - /// - /// Callback argument. - /// Always null. - private static object ActivateFirstInstanceCallback(object o) - { - ActivateFirstInstance(); - return null; + // Connect to the available pipe + await pipeClient.ConnectAsync(0); } /// diff --git a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs index a3bd83a97..151ce97dd 100644 --- a/Flow.Launcher/Helper/WallpaperPathRetrieval.cs +++ b/Flow.Launcher/Helper/WallpaperPathRetrieval.cs @@ -2,19 +2,16 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using System.Windows; using System.Windows.Media; using System.Windows.Media.Imaging; +using Flow.Launcher.Infrastructure; using Microsoft.Win32; -using Windows.Win32; -using Windows.Win32.UI.WindowsAndMessaging; namespace Flow.Launcher.Helper; public static class WallpaperPathRetrieval { - private static readonly int MAX_PATH = 260; private static readonly int MAX_CACHE_SIZE = 3; private static readonly Dictionary<(string, DateTime), ImageBrush> wallpaperCache = new(); @@ -29,7 +26,7 @@ public static class WallpaperPathRetrieval try { - var wallpaperPath = GetWallpaperPath(); + var wallpaperPath = Win32Helper.GetWallpaperPath(); if (wallpaperPath is not null && File.Exists(wallpaperPath)) { // Since the wallpaper file name can be the same (TranscodedWallpaper), @@ -78,17 +75,6 @@ public static class WallpaperPathRetrieval } } - private static unsafe string GetWallpaperPath() - { - var wallpaperPtr = stackalloc char[MAX_PATH]; - PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETDESKWALLPAPER, (uint)MAX_PATH, - wallpaperPtr, - 0); - var wallpaper = MemoryMarshal.CreateReadOnlySpanFromNullTerminated(wallpaperPtr); - - return wallpaper.ToString(); - } - private static Color GetWallpaperColor() { RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Colors", true); diff --git a/Flow.Launcher/Helper/WindowsInteropHelper.cs b/Flow.Launcher/Helper/WindowsInteropHelper.cs deleted file mode 100644 index 3e57948a5..000000000 --- a/Flow.Launcher/Helper/WindowsInteropHelper.cs +++ /dev/null @@ -1,211 +0,0 @@ -using System; -using System.ComponentModel; -using System.Drawing; -using System.Runtime.InteropServices; -using System.Windows; -using System.Windows.Forms; -using System.Windows.Interop; -using System.Windows.Media; -using Windows.Win32; -using Windows.Win32.Foundation; -using Windows.Win32.UI.WindowsAndMessaging; -using Point = System.Windows.Point; - -namespace Flow.Launcher.Helper; - -public class WindowsInteropHelper -{ - private static HWND _hwnd_shell; - private static HWND _hwnd_desktop; - - //Accessors for shell and desktop handlers - //Will set the variables once and then will return them - private static HWND HWND_SHELL - { - get - { - return _hwnd_shell != HWND.Null ? _hwnd_shell : _hwnd_shell = PInvoke.GetShellWindow(); - } - } - - private static HWND HWND_DESKTOP - { - get - { - return _hwnd_desktop != HWND.Null ? _hwnd_desktop : _hwnd_desktop = PInvoke.GetDesktopWindow(); - } - } - - const string WINDOW_CLASS_CONSOLE = "ConsoleWindowClass"; - const string WINDOW_CLASS_WINTAB = "Flip3D"; - const string WINDOW_CLASS_PROGMAN = "Progman"; - const string WINDOW_CLASS_WORKERW = "WorkerW"; - - public unsafe static bool IsWindowFullscreen() - { - //get current active window - var hWnd = PInvoke.GetForegroundWindow(); - - if (hWnd.Equals(HWND.Null)) - { - return false; - } - - //if current active window is desktop or shell, exit early - if (hWnd.Equals(HWND_DESKTOP) || hWnd.Equals(HWND_SHELL)) - { - return false; - } - - string windowClass; - const int capacity = 256; - Span buffer = stackalloc char[capacity]; - int validLength; - fixed (char* pBuffer = buffer) - { - validLength = PInvoke.GetClassName(hWnd, pBuffer, capacity); - } - - windowClass = buffer[..validLength].ToString(); - - - //for Win+Tab (Flip3D) - if (windowClass == WINDOW_CLASS_WINTAB) - { - return false; - } - - PInvoke.GetWindowRect(hWnd, out var appBounds); - - //for console (ConsoleWindowClass), we have to check for negative dimensions - if (windowClass == WINDOW_CLASS_CONSOLE) - { - return appBounds.top < 0 && appBounds.bottom < 0; - } - - //for desktop (Progman or WorkerW, depends on the system), we have to check - if (windowClass is WINDOW_CLASS_PROGMAN or WINDOW_CLASS_WORKERW) - { - var hWndDesktop = PInvoke.FindWindowEx(hWnd, HWND.Null, "SHELLDLL_DefView", null); - hWndDesktop = PInvoke.FindWindowEx(hWndDesktop, HWND.Null, "SysListView32", "FolderView"); - if (hWndDesktop.Value != (IntPtr.Zero)) - { - return false; - } - } - - Rectangle screenBounds = Screen.FromHandle(hWnd).Bounds; - return (appBounds.bottom - appBounds.top) == screenBounds.Height && - (appBounds.right - appBounds.left) == screenBounds.Width; - } - - /// - /// disable windows toolbar's control box - /// this will also disable system menu with Alt+Space hotkey - /// - public static void DisableControlBox(Window win) - { - var hwnd = new HWND(new WindowInteropHelper(win).Handle); - - var style = PInvoke.GetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE); - - if (style == 0) - { - throw new Win32Exception(Marshal.GetLastPInvokeError()); - } - - style &= ~(int)WINDOW_STYLE.WS_SYSMENU; - - var previousStyle = PInvoke.SetWindowLong(hwnd, WINDOW_LONG_PTR_INDEX.GWL_STYLE, - style); - - if (previousStyle == 0) - { - throw new Win32Exception(Marshal.GetLastPInvokeError()); - } - } - - /// - /// Transforms pixels to Device Independent Pixels used by WPF - /// - /// current window, required to get presentation source - /// horizontal position in pixels - /// vertical position in pixels - /// point containing device independent pixels - public static Point TransformPixelsToDIP(Visual visual, double unitX, double unitY) - { - Matrix matrix; - var source = PresentationSource.FromVisual(visual); - if (source is not null) - { - matrix = source.CompositionTarget.TransformFromDevice; - } - else - { - using var src = new HwndSource(new HwndSourceParameters()); - matrix = src.CompositionTarget.TransformFromDevice; - } - - return new Point((int)(matrix.M11 * unitX), (int)(matrix.M22 * unitY)); - } - - #region Alt Tab - - private static int SetWindowLong(HWND hWnd, WINDOW_LONG_PTR_INDEX nIndex, int dwNewLong) - { - PInvoke.SetLastError(WIN32_ERROR.NO_ERROR); // Clear any existing error - - var result = PInvoke.SetWindowLong(hWnd, nIndex, dwNewLong); - if (result == 0 && Marshal.GetLastPInvokeError() != 0) - { - throw new Win32Exception(Marshal.GetLastPInvokeError()); - } - - return result; - } - - /// - /// Hide windows in the Alt+Tab window list - /// - /// To hide a window - public static void HideFromAltTab(Window window) - { - var exStyle = GetCurrentWindowStyle(window); - - // Add TOOLWINDOW style, remove APPWINDOW style - var newExStyle = ((uint)exStyle | (uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) & ~(uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW; - - SetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle); - } - - /// - /// Restore window display in the Alt+Tab window list. - /// - /// To restore the displayed window - public static void ShowInAltTab(Window window) - { - var exStyle = GetCurrentWindowStyle(window); - - // Remove the TOOLWINDOW style and add the APPWINDOW style. - var newExStyle = ((uint)exStyle & ~(uint)WINDOW_EX_STYLE.WS_EX_TOOLWINDOW) | (uint)WINDOW_EX_STYLE.WS_EX_APPWINDOW; - - SetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)newExStyle); - } - - /// - /// To obtain the current overridden style of a window. - /// - /// To obtain the style dialog window - /// current extension style value - private static int GetCurrentWindowStyle(Window window) - { - var style = PInvoke.GetWindowLong(new(new WindowInteropHelper(window).Handle), WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); - if (style == 0 && Marshal.GetLastPInvokeError() != 0) - { - throw new Win32Exception(Marshal.GetLastPInvokeError()); - } - return style; - } - - #endregion -} diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index e1dfc1108..678280bba 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -4,25 +4,16 @@ using System.Collections.ObjectModel; using System.Threading.Tasks; using System.Windows; using System.Windows.Input; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.Resource; using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure.Hotkey; +using Flow.Launcher.Infrastructure.UserSettings; namespace Flow.Launcher { public partial class HotkeyControl { - public IHotkeySettings HotkeySettings { - get { return (IHotkeySettings)GetValue(HotkeySettingsProperty); } - set { SetValue(HotkeySettingsProperty, value); } - } - - public static readonly DependencyProperty HotkeySettingsProperty = DependencyProperty.Register( - nameof(HotkeySettings), - typeof(IHotkeySettings), - typeof(HotkeyControl), - new PropertyMetadata() - ); public string WindowTitle { get { return (string)GetValue(WindowTitleProperty); } set { SetValue(WindowTitleProperty, value); } @@ -71,8 +62,7 @@ namespace Flow.Launcher return; } - hotkeyControl.SetKeysToDisplay(new HotkeyModel(hotkeyControl.Hotkey)); - hotkeyControl.CurrentHotkey = new HotkeyModel(hotkeyControl.Hotkey); + hotkeyControl.RefreshHotkeyInterface(hotkeyControl.Hotkey); } @@ -90,17 +80,132 @@ namespace Flow.Launcher } - public static readonly DependencyProperty HotkeyProperty = DependencyProperty.Register( - nameof(Hotkey), - typeof(string), + public static readonly DependencyProperty TypeProperty = DependencyProperty.Register( + nameof(Type), + typeof(HotkeyType), typeof(HotkeyControl), - new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnHotkeyChanged) + new FrameworkPropertyMetadata(HotkeyType.None, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnHotkeyChanged) ); + public HotkeyType Type + { + get { return (HotkeyType)GetValue(TypeProperty); } + set { SetValue(TypeProperty, value); } + } + + public enum HotkeyType + { + None, + // Custom query hotkeys + CustomQueryHotkey, + // Settings hotkeys + Hotkey, + PreviewHotkey, + OpenContextMenuHotkey, + SettingWindowHotkey, + CycleHistoryUpHotkey, + CycleHistoryDownHotkey, + SelectPrevPageHotkey, + SelectNextPageHotkey, + AutoCompleteHotkey, + AutoCompleteHotkey2, + SelectPrevItemHotkey, + SelectPrevItemHotkey2, + SelectNextItemHotkey, + SelectNextItemHotkey2 + } + + // We can initialize settings in static field because it has been constructed in App constuctor + // and it will not construct settings instances twice + private static readonly Settings _settings = Ioc.Default.GetRequiredService(); + + private string hotkey = string.Empty; public string Hotkey { - get { return (string)GetValue(HotkeyProperty); } - set { SetValue(HotkeyProperty, value); } + get + { + return Type switch + { + // Custom query hotkeys + HotkeyType.CustomQueryHotkey => hotkey, + // Settings hotkeys + HotkeyType.Hotkey => _settings.Hotkey, + HotkeyType.PreviewHotkey => _settings.PreviewHotkey, + HotkeyType.OpenContextMenuHotkey => _settings.OpenContextMenuHotkey, + HotkeyType.SettingWindowHotkey => _settings.SettingWindowHotkey, + HotkeyType.CycleHistoryUpHotkey => _settings.CycleHistoryUpHotkey, + HotkeyType.CycleHistoryDownHotkey => _settings.CycleHistoryDownHotkey, + HotkeyType.SelectPrevPageHotkey => _settings.SelectPrevPageHotkey, + HotkeyType.SelectNextPageHotkey => _settings.SelectNextPageHotkey, + HotkeyType.AutoCompleteHotkey => _settings.AutoCompleteHotkey, + HotkeyType.AutoCompleteHotkey2 => _settings.AutoCompleteHotkey2, + HotkeyType.SelectPrevItemHotkey => _settings.SelectPrevItemHotkey, + HotkeyType.SelectPrevItemHotkey2 => _settings.SelectPrevItemHotkey2, + HotkeyType.SelectNextItemHotkey => _settings.SelectNextItemHotkey, + HotkeyType.SelectNextItemHotkey2 => _settings.SelectNextItemHotkey2, + _ => throw new System.NotImplementedException("Hotkey type not set") + }; + } + set + { + switch (Type) + { + // Custom query hotkeys + case HotkeyType.CustomQueryHotkey: + // We just need to store it in a local field + // because we will save to settings in other place + hotkey = value; + break; + // Settings hotkeys + case HotkeyType.Hotkey: + _settings.Hotkey = value; + break; + case HotkeyType.PreviewHotkey: + _settings.PreviewHotkey = value; + break; + case HotkeyType.OpenContextMenuHotkey: + _settings.OpenContextMenuHotkey = value; + break; + case HotkeyType.SettingWindowHotkey: + _settings.SettingWindowHotkey = value; + break; + case HotkeyType.CycleHistoryUpHotkey: + _settings.CycleHistoryUpHotkey = value; + break; + case HotkeyType.CycleHistoryDownHotkey: + _settings.CycleHistoryDownHotkey = value; + break; + case HotkeyType.SelectPrevPageHotkey: + _settings.SelectPrevPageHotkey = value; + break; + case HotkeyType.SelectNextPageHotkey: + _settings.SelectNextPageHotkey = value; + break; + case HotkeyType.AutoCompleteHotkey: + _settings.AutoCompleteHotkey = value; + break; + case HotkeyType.AutoCompleteHotkey2: + _settings.AutoCompleteHotkey2 = value; + break; + case HotkeyType.SelectPrevItemHotkey: + _settings.SelectPrevItemHotkey = value; + break; + case HotkeyType.SelectNextItemHotkey: + _settings.SelectNextItemHotkey = value; + break; + case HotkeyType.SelectPrevItemHotkey2: + _settings.SelectPrevItemHotkey2 = value; + break; + case HotkeyType.SelectNextItemHotkey2: + _settings.SelectNextItemHotkey2 = value; + break; + default: + throw new System.NotImplementedException("Hotkey type not set"); + } + + // After setting the hotkey, we need to refresh the interface + RefreshHotkeyInterface(Hotkey); + } } public HotkeyControl() @@ -108,7 +213,15 @@ namespace Flow.Launcher InitializeComponent(); HotkeyList.ItemsSource = KeysToDisplay; - SetKeysToDisplay(CurrentHotkey); + + // We should not call RefreshHotkeyInterface here because DependencyProperty is not set yet + // And it will be called in OnHotkeyChanged event or Hotkey setter later + } + + private void RefreshHotkeyInterface(string hotkey) + { + SetKeysToDisplay(new HotkeyModel(hotkey)); + CurrentHotkey = new HotkeyModel(hotkey); } private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => @@ -133,7 +246,7 @@ namespace Flow.Launcher HotKeyMapper.RemoveHotkey(Hotkey); } - var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, HotkeySettings, WindowTitle); + var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, WindowTitle); await dialog.ShowAsync(); switch (dialog.ResultType) { diff --git a/Flow.Launcher/HotkeyControlDialog.xaml.cs b/Flow.Launcher/HotkeyControlDialog.xaml.cs index a4d21a782..2f8c5eb26 100644 --- a/Flow.Launcher/HotkeyControlDialog.xaml.cs +++ b/Flow.Launcher/HotkeyControlDialog.xaml.cs @@ -4,9 +4,11 @@ using System.Linq; using System.Windows; using System.Windows.Input; using ChefKeys; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Core.Resource; using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure.Hotkey; +using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using ModernWpf.Controls; @@ -16,7 +18,7 @@ namespace Flow.Launcher; public partial class HotkeyControlDialog : ContentDialog { - private IHotkeySettings _hotkeySettings; + private static readonly IHotkeySettings _hotkeySettings = Ioc.Default.GetRequiredService(); private Action? _overwriteOtherHotkey; private string DefaultHotkey { get; } public string WindowTitle { get; } @@ -36,7 +38,7 @@ public partial class HotkeyControlDialog : ContentDialog private static bool isOpenFlowHotkey; - public HotkeyControlDialog(string hotkey, string defaultHotkey, IHotkeySettings hotkeySettings, string windowTitle = "") + public HotkeyControlDialog(string hotkey, string defaultHotkey, string windowTitle = "") { WindowTitle = windowTitle switch { @@ -45,7 +47,6 @@ public partial class HotkeyControlDialog : ContentDialog }; DefaultHotkey = defaultHotkey; CurrentHotkey = new HotkeyModel(hotkey); - _hotkeySettings = hotkeySettings; SetKeysToDisplay(CurrentHotkey); InitializeComponent(); diff --git a/Flow.Launcher/Languages/ar.xaml b/Flow.Launcher/Languages/ar.xaml index fa5c968d4..a57179da6 100644 --- a/Flow.Launcher/Languages/ar.xaml +++ b/Flow.Launcher/Languages/ar.xaml @@ -13,6 +13,7 @@ فشل في تسجيل مفتاح التشغيل السريع "{0}". قد يكون المفتاح مستخدمًا من قبل برنامج آخر. قم بتغيير المفتاح، أو قم بإغلاق البرنامج الآخر. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher تعذر بدء {0} تنسيق ملف إضافة Flow Launcher غير صالح @@ -44,6 +45,8 @@ وضع المحمول تخزين جميع الإعدادات وبيانات المستخدم في مجلد واحد (مفيد عند استخدام الأقراص القابلة للإزالة أو الخدمات السحابية). تشغيل Flow Launcher عند بدء تشغيل النظام + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler خطأ في إعداد التشغيل عند بدء التشغيل إخفاء Flow Launcher عند فقدان التركيز عدم عرض إشعارات الإصدار الجديد @@ -126,7 +129,8 @@ الإصدار الموقع الإلكتروني إلغاء التثبيت - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually متجر الإضافات @@ -143,8 +147,6 @@ تم تحديث هذه الإضافة في آخر 7 أيام يتوفر تحديث جديد - - السمة المظهر @@ -194,7 +196,6 @@ هذه السمة تدعم الوضعين (فاتح/داكن). هذه السمة تدعم الخلفية الضبابية الشفافة. - مفتاح الاختصار مفاتيح الاختصار @@ -297,6 +298,9 @@ موقع بيانات المستخدم يتم حفظ إعدادات المستخدم والإضافات المثبتة في مجلد بيانات المستخدم. قد يختلف هذا الموقع اعتمادًا على ما إذا كان في وضع النقل أم لا. فتح المجلد + Log Level + Debug + Info اختر مدير الملفات @@ -367,6 +371,7 @@ حسناً نعم لا + الخلفية الإصدار @@ -383,6 +388,9 @@ تم إرسال التقرير بنجاح فشل في إرسال التقرير حدث خطأ في Flow Launcher + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message يرجى الانتظار... diff --git a/Flow.Launcher/Languages/cs.xaml b/Flow.Launcher/Languages/cs.xaml index 05adf095b..92721b70a 100644 --- a/Flow.Launcher/Languages/cs.xaml +++ b/Flow.Launcher/Languages/cs.xaml @@ -13,6 +13,7 @@ Nepodařilo se zaregistrovat hotkey "{0}". Klávesová zkratka může být používána jiným programem. Změňte na jinou klávesu nebo ukončíte jiný program. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher Nepodařilo se spustit {0} Neplatný typ souboru pluginu aplikace Flow Launcher @@ -44,6 +45,8 @@ Přenosný režim Ukládat všechna nastavení a uživatelská data v jedné složce (Užitečné při užití s přenosnými zařízeními). Spustit Flow Launcher při spuštění systému + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Při nastavování spouštění došlo k chybě Skrýt Flow Launcher při vykliknutí Nezobrazovat oznámení o nové verzi @@ -126,7 +129,8 @@ Verze Webová stránka Odinstalovat - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Obchod s pluginy @@ -143,8 +147,6 @@ Tento plugin byl aktualizován během posledních 7 dní Nová aktualizace je k dispozici - - Motiv Vzhled @@ -194,7 +196,6 @@ This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. - Klávesová zkratka Klávesové zkratky @@ -297,6 +298,9 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Log Level + Debug + Info Vybrat správce souborů @@ -367,6 +371,7 @@ Pokud před zkratku při zadávání přidáte znak "@", bude odpovíd Dobře Yes No + Pozadí Verze @@ -383,6 +388,9 @@ Pokud před zkratku při zadávání přidáte znak "@", bude odpovíd Hlášení bylo úspěšně odesláno Nepodařilo se odeslat hlášení Flow Launcher zaznamenal chybu + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Počkejte prosím... diff --git a/Flow.Launcher/Languages/da.xaml b/Flow.Launcher/Languages/da.xaml index 36970ad53..629173f74 100644 --- a/Flow.Launcher/Languages/da.xaml +++ b/Flow.Launcher/Languages/da.xaml @@ -13,6 +13,7 @@ Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher Kunne ikke starte {0} Ugyldigt Flow Launcher plugin filformat @@ -44,6 +45,8 @@ Portable Mode Store all settings and user data in one folder (Useful when used with removable drives or cloud services). Start Flow Launcher ved system start + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Error setting launch on startup Skjul Flow Launcher ved mistet fokus Vis ikke notifikationer om nye versioner @@ -126,7 +129,8 @@ Version Website Uninstall - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Plugin Store @@ -143,8 +147,6 @@ This plugin has been updated within the last 7 days New Update is Available - - Tema Appearance @@ -194,7 +196,6 @@ This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. - Genvejstast Genvejstast @@ -297,6 +298,9 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Log Level + Debug + Info Select File Manager @@ -367,6 +371,7 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in OK Yes No + Background Version @@ -383,6 +388,9 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in Rapport sendt korrekt Kunne ikke sende rapport Flow Launcher fik en fejl + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Please wait... diff --git a/Flow.Launcher/Languages/de.xaml b/Flow.Launcher/Languages/de.xaml index c7e775ae3..8a7e3498a 100644 --- a/Flow.Launcher/Languages/de.xaml +++ b/Flow.Launcher/Languages/de.xaml @@ -13,6 +13,7 @@ Hotkey "{0}" konnte nicht registriert werden. Der Hotkey ist möglicherweise von einem anderen Programm in Verwendung. Wechseln Sie zu einem anderen Hotkey oder beenden Sie das andere Programm. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher Konnte nicht gestartet werden {0} Flow Launcher Plug-in-Dateiformat ungültig @@ -44,6 +45,8 @@ Portabler Modus Speichern Sie alle Einstellungen und Benutzerdaten in einem Ordner (nützlich bei Verwendung von Wechsellaufwerken oder Cloud-Diensten). Flow Launcher bei Systemstart starten + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Fehler bei Einstellungsstart bei Start Flow Launcher ausblenden, wenn Fokus verloren geht Versionsbenachrichtigungen nicht zeigen @@ -126,7 +129,8 @@ Version Website Deinstallieren - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Plug-in-Store @@ -143,8 +147,6 @@ Dieses Plug-in ist innerhalb der letzten 7 Tage aktualisiert worden Neues Update ist verfügbar - - Theme Erscheinungsbild @@ -194,7 +196,6 @@ Dieses Theme unterstützt zwei Modi (hell/dunkel). Dieses Theme unterstützt Unschärfe und transparenten Hintergrund. - Hotkey Hotkeys @@ -297,6 +298,9 @@ Speicherort für Benutzerdaten Benutzereinstellungen und installierte Plug-ins werden im Ordner für Benutzerdaten gespeichert. Dieser Speicherort kann variieren, je nachdem, ob sich das Programm im portablen Modus befindet oder nicht. Ordner öffnen + Log Level + Debug + Info Dateimanager auswählen @@ -367,6 +371,7 @@ Wenn Sie bei der Eingabe eines Shortcuts ein '@'-Präfix hinzufügen, stimmt die OK Ja Nein + Hintergrund Version @@ -383,6 +388,9 @@ Wenn Sie bei der Eingabe eines Shortcuts ein '@'-Präfix hinzufügen, stimmt die Bericht erfolgreich gesendet Bericht konnte nicht gesendet werden Flow Launcher hat einen Fehler + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Bitte warten Sie ... diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index a3f87cd30..f0454b496 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -104,6 +104,11 @@ Always Preview Always open preview panel when Flow activates. Press {0} to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled + Backdrop Type + None + Acrylic + Mica + Mica Alt Search Plugin @@ -337,9 +342,10 @@ Can't find specified plugin New Action Keyword can't be empty This new Action Keyword is already assigned to another plugin, please choose a different one + This new Action Keyword is the same as old, please choose a different one Success Completed successfully - Enter the action keyword you like to use to start the plugin. Use * if you don't want to specify any, and the plugin will be triggered without any action keywords. + Enter the action keywords you like to use to start the plugin and use whitespace to divide them. Use * if you don't want to specify any, and the plugin will be triggered without any action keywords. Custom Query Hotkey diff --git a/Flow.Launcher/Languages/es-419.xaml b/Flow.Launcher/Languages/es-419.xaml index 0556c59ae..1ab69727b 100644 --- a/Flow.Launcher/Languages/es-419.xaml +++ b/Flow.Launcher/Languages/es-419.xaml @@ -13,6 +13,7 @@ Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher No se pudo iniciar {0} Formato de archivo de plugin Flow Launcher inválido @@ -44,6 +45,8 @@ Modo portable Almacena todos los ajustes y datos de usuario en una sola carpeta (útil cuando se utiliza con unidades extraíbles o servicios en la nube). Iniciar Flow Launcher al arrancar el sistema + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Error setting launch on startup Ocultar Flow Launcher cuando se pierde el enfoque No mostrar notificaciones de nuevas versiones @@ -126,7 +129,8 @@ Versión Sitio web Uninstall - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Tienda de Plugins @@ -143,8 +147,6 @@ This plugin has been updated within the last 7 days New Update is Available - - Tema Appearance @@ -194,7 +196,6 @@ This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. - Tecla Rápida Tecla Rápida @@ -297,6 +298,9 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Log Level + Debug + Info Seleccionar Gestor de Archivos @@ -367,6 +371,7 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in OK Yes No + Background Versión @@ -383,6 +388,9 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in Informe enviado correctamente Error al enviar el informe Flow Launcher ha tenido un error + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Por favor espere... diff --git a/Flow.Launcher/Languages/es.xaml b/Flow.Launcher/Languages/es.xaml index c7595f69b..4cba4c8a7 100644 --- a/Flow.Launcher/Languages/es.xaml +++ b/Flow.Launcher/Languages/es.xaml @@ -13,6 +13,7 @@ No se ha podido registrar el atajo de teclado "{0}". El atajo de teclado puede estar siendo utilizado por otro programa. Seleccione un atajo de teclado diferente o salga del otro programa. + No se ha podido anular el registro de la tecla de acceso rápido «{0}». Inténtelo de nuevo o consulte el registro para obtener más detalles Flow Launcher No se ha podido iniciar {0} Formato de archivo del complemento de Flow Launcher no válido @@ -44,6 +45,8 @@ Modo Portable Guarda toda la configuración y datos de usuario en una carpeta (Útil cuando se utiliza con unidades extraíbles o servicios en la nube). Cargar Flow Launcher al iniciar el sistema + Usar la tarea de inicio de sesión en lugar de la entrada de inicio para una experiencia de inicio más rápida + Después de la desinstalación, es necesario eliminar manualmente la tarea (Flow.Launcher Startup) mediante el Programador de Tareas Error de configuración de arranque al iniciar Ocultar Flow Launcher cuando se pierde el foco No mostrar notificaciones de nuevas versiones @@ -126,7 +129,8 @@ Versión Sitio web Desinstalar - + Fallo al eliminar la configuración del complemento + Complementos: {0} - Fallo al eliminar los archivos de configuración del complemento, por favor elimínelos manualmente Tienda complementos @@ -143,8 +147,6 @@ Este complemento ha sido actualizado en los últimos 7 días Nueva actualización disponible - - Tema Apariencia @@ -194,7 +196,6 @@ Este tema soporta dos modos (claro/oscuro). Este tema soporta fondo transparente desenfocado. - Atajo de teclado Atajos de teclado @@ -297,6 +298,9 @@ Ubicación de datos del usuario La configuración del usuario y los complementos instalados se guardan en la carpeta de datos del usuario. Esta ubicación puede variar dependiendo de si está en modo portable o no. Abrir carpeta + Nivel de registro + Depurar + Información Seleccionar administrador de archivos @@ -367,6 +371,7 @@ Si añade un prefijo "@" al introducir un acceso directo, éste coinci Aceptar Si No + Fondo Versión @@ -383,6 +388,9 @@ Si añade un prefijo "@" al introducir un acceso directo, éste coinci Informe enviado correctamente No se ha podido enviar el informe Flow Launcher ha tenido un error + Por favor, abra un nuevo tema en + 1. Subir archivo de registro: {0} + 2. Copiar el siguiente mensaje de excepción Por favor espere... diff --git a/Flow.Launcher/Languages/fr.xaml b/Flow.Launcher/Languages/fr.xaml index e978e91ec..d0d1d010d 100644 --- a/Flow.Launcher/Languages/fr.xaml +++ b/Flow.Launcher/Languages/fr.xaml @@ -13,6 +13,7 @@ Échec lors de l'enregistrement du raccourci : {0} + Échec de la réinitialisation du raccourci "{0}". Veuillez réessayer ou consulter le journal pour plus de détails Flow Launcher Impossible de lancer {0} Le format de fichier n'est pas un plugin Flow Launcher valide @@ -44,6 +45,8 @@ Mode Portable Stocker tous les paramètres et données utilisateur dans un seul dossier (Pratique en cas d'utilisation de disques amovibles ou de services cloud). Lancer Flow Launcher au démarrage du système + Utilisez la tâche de connexion au lieu de l'entrée de démarrage pour une expérience de démarrage plus rapide + Après une désinstallation, vous devez supprimer manuellement cette tâche (Flow.Launcher Startup) via le planificateur de tâches Erreur lors de la configuration du lancement au démarrage Cacher Flow Launcher lors de la perte de focus Ne pas afficher le message de mise à jour pour les nouvelles versions @@ -126,7 +129,8 @@ Version Site Web Désinstaller - + Échec de la suppression des paramètres du plugin + Plugins : {0} - Échec de la suppression des fichiers de configuration des plugins, veuillez les supprimer manuellement Magasin des Plugins @@ -143,8 +147,6 @@ Cette extension a été mis à jour au cours des 7 derniers jours Une nouvelle mise à jour est disponible - - Thèmes Apparence @@ -194,7 +196,6 @@ Ce thème prend en charge deux modes (clair/sombre). Ce thème prend en charge l'arrière-plan flou et transparent. - Raccourcis Raccourcis @@ -296,6 +297,9 @@ Emplacement des données utilisateur Les paramètres utilisateur et les plugins installés sont enregistrés dans le dossier des données utilisateur. Cet emplacement peut varier selon que vous soyez en mode portable ou non. Ouvrir le dossier + Niveau de journalisation + Débogage + Info Sélectionner le gestionnaire de fichiers @@ -326,7 +330,7 @@ Ancien mot-clé d'action Nouveau mot-clé d'action Annuler - Termin + Terminé Impossible de trouver le module spécifi Le nouveau mot-clé d'action doit être spécifi Le nouveau mot-clé d'action a été assigné à un autre module, veuillez en choisir un autre @@ -366,6 +370,7 @@ Si vous ajoutez un préfixe "@" lors de la saisie d'un raccourci, celu Ok Oui Non + Arrière-plan Version @@ -382,6 +387,9 @@ Si vous ajoutez un préfixe "@" lors de la saisie d'un raccourci, celu Signalement envoy Échec de l'envoi du signalement Flow Launcher a rencontré une erreur + Veuillez ouvrir un nouveau ticket dans + 1. Télécharger le fichier journal : {0} + 2. Copiez le message d’exception ci-dessous Veuillez patienter... diff --git a/Flow.Launcher/Languages/he.xaml b/Flow.Launcher/Languages/he.xaml index f3c9bb307..c912052f1 100644 --- a/Flow.Launcher/Languages/he.xaml +++ b/Flow.Launcher/Languages/he.xaml @@ -2,9 +2,9 @@ - Flow detected you have installed {0} plugins, which will require {1} to run. Would you like to download {1}? + Flow זיהה שהתקנת את התוסף {0}, אשר דורש את {1} כדי לפעול. האם תרצה להוריד את {1}? {2}{2} - Click no if it's already installed, and you will be prompted to select the folder that contains the {1} executable + אם זה כבר מותקן, לחץ על 'לא' ותתבקש לבחור את התיקיה המכילה את קובץ ההפעלה {1} אנא בחר את קובץ ההפעלה {0} לא ניתן להגדיר נתיב הפעלה {0}, אנא נסה שוב בהגדרות Flow (גלול עד למטה). @@ -13,13 +13,14 @@ רישום מקש הקיצור "{0}" נכשל. ייתכן שמקש הקיצור נמצא בשימוש על ידי תוכנה אחרת. שנה למקש קיצור אחר, או צא מהתוכנה האחרת. + ביטול הרישום של מקש קיצור "{0}" נכשל. אנא נסה שוב או עיין ביומן הרישום לפרטים נוספים Flow Launcher לא ניתן היה להפעיל את {0} פורמט קובץ תוסף Flow Launcher לא חוקי הגדר כגבוה ביותר בשאילתה זו בטל העלאה בשאילתה זו בצע שאילתה: {0} - Last execution time: {0} + זמן ביצוע אחרון: {0} פתח הגדרות אודות @@ -28,15 +29,15 @@ העתק גזור הדבק - Undo + בטל בחר הכל קובץ תיקייה טקסט מצב משחק השהה את השימוש במקשי קיצור. - Position Reset - Reset search window position + איפוס מיקום + אפס את מיקום חלון החיפוש הגדרות @@ -44,6 +45,8 @@ מצב נייד אחסן את כל ההגדרות ונתוני המשתמש בתיקייה אחת (שימושי בשימוש עם כוננים נשלפים או שירותי ענן). הפעל את Flow Launcher בעת הפעלת Window + השתמש במשימת כניסה במקום בכניסה בעת האתחול, לחוויית הפעלה מהירה יותר + לאחר הסרת ההתקנה, עליך להסיר ידנית משימה זו (Flow.Launcher Startup) דרך מתזמן המשימות שגיאה בהגדרת ההפעלה בעת הפעלת windows הסתר את Flow Launcher כאשר הוא אינו החלון הפעיל אל תציג התראות על גרסה חדשה @@ -60,179 +63,177 @@ ימין עליון Custom Position שפה - Last Query Style - Show/Hide previous results when Flow Launcher is reactivated. - Preserve Last Query - Select last Query - Empty last Query - Preserve Last Action Keyword - Select Last Action Keyword - Fixed Window Height - The window height is not adjustable by dragging. - Maximum results shown - You can also quickly adjust this by using CTRL+Plus and CTRL+Minus. - Ignore hotkeys in fullscreen mode - Disable Flow Launcher activation when a full screen application is active (Recommended for games). - Default File Manager - Select the file manager to use when opening the folder. - Default Web Browser - Setting for New Tab, New Window, Private Mode. - Python Path - Node.js Path - Please select the Node.js executable - Please select pythonw.exe - Always Start Typing in English Mode - Temporarily change your input method to English mode when activating Flow. + סגנון שאילתה אחרונה + הצג/הסתר תוצאות קודמות כאשר Flow Launcher מופעל מחדש. + שמור את השאילתה האחרונה + בחר שאילתא אחרונה + נקה שאילתא אחרונה + שמור מילת מפתח לפעולה האחרונה + בחר מילת מפתח לפעולה האחרונה + גובה חלון קבוע + גובה החלון אינו ניתן להתאמה באמצעות גרירה. + כמות תוצאות מרבית + ניתן גם להתאים במהירות באמצעות CTRL+פלוס ו-CTRL+מינוס. + התעלם מקיצורי מקשים במצב מסך מלא + השבת את הפעלת Flow Launcher כאשר יישום מסך מלא פעיל (מומלץ למשחקים). + מנהל הקבצים המוגדר כברירת מחדל + בחר את מנהל הקבצים לשימוש בעת פתיחת תיקיה. + דפדפן ברירת מחדל + הגדרה ללשונית חדשה, חלון חדש, מצב פרטי. + נתיב Python + נתיב Node.js + בחר את קובץ ההפעלה של Node.js + בחר את pythonw.exe + תמיד התחל להקליד במצב אנגלית + שנה זמנית את שיטת הקלט שלך למצב אנגלית בעת הפעלת Flow. עדכון אוטומטי בחר - Hide Flow Launcher on startup - Flow Launcher search window is hidden in the tray after starting up. - Hide tray icon - When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. - Query Search Precision - Changes minimum match score required for results. + הסתר את Flow Launcher בהפעלת המחשב + חלון החיפוש של Flow Launcher מוסתר במגש לאחר ההפעלה. + הסתר אייקון מגש + כאשר האייקון מוסתר מהמגש, ניתן לפתוח את תפריט ההגדרות על ידי לחיצה ימנית על חלון החיפוש. + דיוק חיפוש שאילתה + משנה את ציון ההתאמה המינימלי הנדרש לתוצאות. ללא נמוך Regular Search with Pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. - Always Preview - Always open preview panel when Flow activates. Press {0} to toggle preview. - Shadow effect is not allowed while current theme has blur effect enabled + הצג תמיד תצוגה מקדימה + פתח תמיד את לוח התצוגה המקדימה כאשר Flow מופעל. הקש על {0} כדי להחליף את התצוגה המקדימה. + לא ניתן להחיל אפקט צל כאשר העיצוב הנוכחי מוגדר לאפקט טשטוש - Search Plugin - Ctrl+F to search plugins - No results found + חפש תוסף + Ctrl+F לחיפוש תוסף + לא נמצאו תוצאות Please try a different search. - Plugin + תוסף תוספים מצא תוספים נוספים - On - Off - Action keyword Setting - Action keyword - Current action keyword - New action keyword - Change Action Keywords - Current Priority - New Priority - Priority - Change Plugin Results Priority - Plugin Directory + פועל + כבוי + הגדרת מילת מפתח לפעולה + מילת מפתח לפעולה + מילת מפתח נוכחית לפעולה + מילת מפתח חדשה לפעולה + שנה מילות מפתח לפעולה + עדיפות נוכחית + עדיפות חדשה + עדיפות + שנה עדיפות תוצאות תוסף + ספריית תוספים מאת - Init time: - Query time: + זמן פתיחה: + זמן שאילתא: גרסה אתר הסר התקנה - + נכשל בהסרת הגדרות התוסף + תוספים: {0} - נכשל בהסרת קבצי הגדרות התוסף, יש להסירם ידנית חנות תוספים שחרור חדש - Recently Updated + עודכן לאחרונה תוספים מותקן רענן התקן הסר התקנה עדכון - Plugin already installed + התוסף כבר מותקן גרסה חדשה - This plugin has been updated within the last 7 days - New Update is Available - - + תוסף זה עודכן במהלך 7 הימים האחרונים + עדכון חדש זמין ערכת נושא - Appearance + מראה גלריית ערכות נושא - How to create a theme - Hi There - Explorer - Search for files, folders and file contents - WebSearch + איך ליצור ערכת נושא + שלום + סייר + חפש קבצים, תיקיות ובתוכן הקבצים + חיפוש באינטרנט Search the web with different search engine support - Program - Launch programs as admin or a different user + תוכנה + הפעל תוכנות כמנהל או כמשתמש אחר ProcessKiller - Terminate unwanted processes - Search Bar Height - Item Height - Query Box Font - Result Title Font - Result Subtitle Font + הפסקת תהליכים לא רצויים + גובה סרגל החיפוש + גובה פריט + גופן תיבת שאילתות + גופן הכותרת לתוצאה + גופן כותרת המשנה לתוצאה אפס - Customize - Window Mode - Opacity - Theme {0} not exists, fallback to default theme - Fail to load theme {0}, fallback to default theme - Theme Folder - Open Theme Folder - Color Scheme - System Default + התאם אישית + מצב חלון + שקיפות + ערכת הנושא {0} אינה קיימת, חוזר לערכת ברירת המחדל + נכשל בטעינת העיצוב {0}, חוזר לערכת ברירת המחדל + תיקיית ערכת נושא + פתח תיקיית ערכת נושא + ערכת צבעים + ברירת המחדל של המערכת בהיר כהה - Sound Effect - Play a small sound when the search window opens - Sound Effect Volume - Adjust the volume of the sound effect - Windows Media Player is unavailable and is required for Flow's volume adjustment. Please check your installation if you need to adjust volume. - Animation - Use Animation in UI - Animation Speed - The speed of the UI animation - Slow - Medium - Fast - Custom - Clock - Date - This theme supports two(light/dark) modes. - This theme supports Blur Transparent Background. - + אפקט צליל + השמע צליל קטן כאשר חלון החיפוש נפתח + עוצמת אפקט הקול + התאם את עוצמת אפקט הקול + Windows Media Player אינו זמין, והוא נדרש להתאמת עוצמת הקול של Flow. אנא בדוק את ההתקנה שלך אם אתה צריך להתאים את עוצמת הקול. + אנימציה + השתמש באנימציה בממשק המשתמש + מהירות אנימציה + מהירות האנימציה של ממשק המשתמש + איטי + בינוני + מהיר + מותאם אישית + שעון + תאריך + ערכת נושא זאת תומך בשני מצבים (בהיר/כהה). + ערכת נושא זו תומכת בטשטוש רקע שקוף. מקש קיצור מקשי קיצור פתח את Flow Launcher - Enter shortcut to show/hide Flow Launcher. - Toggle Preview - Enter shortcut to show/hide preview in search window. - Hotkey Presets - List of currently registered hotkeys - Open Result Modifier Key - Select a modifier key to open selected result via keyboard. + הזן קיצור דרך להצגה/הסתרה של Flow Launcher. + הצג/הסתר תצוגה מקדימה + הזן קיצור דרך להצגה/הסתרה של התצוגה המקדימה בחלון החיפוש. + קיצורי דרך מוגדרים + רשימת קיצורי הדרך הרשומים כעת + מקש משני לפתיחת תוצאה + בחר מקש משני לפתיחת התוצאה שנבחרה דרך המקלדת. הצג מקש קיצור - Show result selection hotkey with results. - Auto Complete - Runs autocomplete for the selected items. - Select Next Item - Select Previous Item + הצג מקש קיצור לבחירת תוצאה עם התוצאות. + השלמה אוטומטית + מבצע השלמה אוטומטית לפריטים שנבחרו. + בחר את הפריט הבא + בחר את הפריט הקודם הדף הבא הדף הקודם - Cycle Previous Query - Cycle Next Query - Open Context Menu - Open Native Context Menu - Open Setting Window + עבור לשאילתה הקודמת + עבור לשאילתה הבאה + פתח תפריט הקשר + פתח תפריט הקשר מקומי + פתח חלון הגדרות העתק את נתיב הקובץ - Toggle Game Mode - Toggle History - Open Containing Folder + הפעל או כבה מצב משחק + הפעל או כבה היסטוריה + פתח תיקייה מכילה הרץ כמנהל - Refresh Search Results - Reload Plugins Data - Quick Adjust Window Width - Quick Adjust Window Height - Use when require plugins to reload and update their existing data. - You can add one more hotkey for this function. - Custom Query Hotkeys - Custom Query Shortcuts - Built-in Shortcuts + רענן תוצאות חיפוש + טען מחדש נתוני תוספים + כוונון מהיר של רוחב החלון + כוונון מהיר של גובה החלון + השתמש כאשר יש צורך בטעינה מחדש של תוספים ובעדכון הנתונים שלהם. + באפשרותך להוסיף מקש קיצור נוסף לפעולה זו. + מקשי קיצור לשאילתות מותאמות אישית + קיצורי דרך לשאילתות מותאמות אישית + קיצורי דרך מובנים שאילתה קיצור דרך הרחבה @@ -242,33 +243,33 @@ הוסף ללא אנא בחר פריט - Are you sure you want to delete {0} plugin hotkey? - Are you sure you want to delete shortcut: {0} with expansion {1}? - Get text from clipboard. - Get path from active explorer. - Query window shadow effect - Shadow effect has a substantial usage of GPU. Not recommended if your computer performance is limited. - Window Width Size - You can also quickly adjust this by using Ctrl+[ and Ctrl+]. - Use Segoe Fluent Icons - Use Segoe Fluent Icons for query results where supported - Press Key + האם אתה בטוח שברצונך למחוק את מקש הקיצור של התוסף {0}? + האם אתה בטוח שברצונך למחוק את הקיצור: {0} עם ההרחבה {1}? + קבל טקסט מהלוח. + קבל נתיב מסייר הקבצים הפעיל. + אפקט צל לחלון השאילתה + לאפקט הצל יש שימוש ניכר ב-GPU. לא מומלץ אם ביצועי המחשב שלך מוגבלים. + רוחב החלון + ניתן גם להתאים במהירות באמצעות Ctrl+[ ו-Ctrl+] + השתמש ב-Segoe Fluent Icons + השתמש ב-Segoe Fluent Icons לתוצאות חיפוש כאשר נתמך + הקש על מקש HTTP Proxy - Enable HTTP Proxy - HTTP Server + הפעל HTTP Proxy + שרת HTTP Port שם משתמש סיסמא - Test Proxy + בדוק Proxy שמור - Server field can't be empty - Port field can't be empty - Invalid port format - Proxy configuration saved successfully - Proxy configured correctly - Proxy connection failed + שדה השרת לא יכול להיות ריק + שדה ה-Port לא יכול להיות ריק + פורמט ה-Port לא תקין + תצורת ה-Proxy נשמרה בהצלחה + ה-Proxy הוגדר בהצלחה + החיבור ל- Proxy נכשל אודות @@ -277,182 +278,189 @@ תיעוד גרסה סמלים - You have activated Flow Launcher {0} times - Check for Updates - Become A Sponsor - New version {0} is available, would you like to restart Flow Launcher to use the update? + הפעלת את Flow Launcher {0} פעמים + בדוק עדכונים + תן חסות + גרסה חדשה {0} זמינה, האם ברצונך להפעיל מחדש את Flow Launcher כדי להשתמש בעדכון? בדיקת העדכונים נכשלה, אנא בדוק את הגדרות החיבור ואת הגדרות ה-Proxy שלך לכתובת api.github.com. - Download updates failed, please check your connection and proxy settings to github-cloud.s3.amazonaws.com, - or go to https://github.com/Flow-Launcher/Flow.Launcher/releases to download updates manually. + הורדת העדכונים נכשלה, אנא בדוק את הגדרות החיבור ואת ה-proxy שלך אל github-cloud.s3.amazonaws.com, + או עבור אל https://github.com/Flow-Launcher/Flow.Launcher/releases כדי להוריד עדכונים באופן ידני. - Release Notes - Usage Tips + הערות שחרור + טיפים לשימוש DevTools - Setting Folder - Log Folder - Clear Logs - Are you sure you want to delete all logs? + תיקיית ההגדרות + תיקיית יומני רישום + נקה יומני רישום + האם אתה בטוח שברצונך למחוק את כל היומנים? אשף - User Data Location - User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. - Open Folder + מיקום נתוני משתמש + הגדרות המשתמש והתוספים המותקנים נשמרים בתיקיית נתוני המשתמש. מיקום זה עשוי להשתנות אם התוכנה במצב נייד. + פתח תיקיה + Log Level + Debug + Info - Select File Manager - Please specify the file location of the file manager you using and add arguments as required. The "%d" represents the directory path to open for, used by the Arg for Folder field and for commands opening specific directories. The "%f" represents the file path to open for, used by the Arg for File field and for commands opening specific files. - For example, if the file manager uses a command such as "totalcmd.exe /A c:\windows" to open the c:\windows directory, the File Manager Path will be totalcmd.exe, and the Arg For Folder will be /A "%d". Certain file managers like QTTabBar may just require a path to be supplied, in this instance use "%d" as the File Manager Path and leave the rest of the fileds blank. + בחר מנהל קבצים + אנא ציין את מיקום הקובץ של מנהל הקבצים שבו אתה משתמש והוסף ארגומנטים כנדרש. "%d" מייצג את נתיב התיקייה שיש לפתוח, ומשמש בשדה ארגומנט לתיקייה ובפקודות לפתיחת תיקיות מסוימות. "%f" מייצג את נתיב הקובץ שיש לפתוח, ומשמש בשדה ארגומנט לקובץ ובפקודות לפתיחת קבצים מסוימים. + לדוגמה, אם מנהל הקבצים משתמש בפקודה כגון "totalcmd.exe /A c:\windows" כדי לפתוח את התיקייה c:\windows, נתיב מנהל הקבצים יהיה totalcmd.exe, והארגומנט לתיקייה יהיה /A "%d". מנהלי קבצים מסוימים, כגון QTTabBar, עשויים לדרוש רק ציון נתיב, במקרה כזה השתמש ב-"%d" כנתיב מנהל הקבצים והשאר את שאר השדות ריקים. מנהל קבצים שם פרופיל - File Manager Path - Arg For Folder - Arg For File + נתיב מנהל קבצים + ארגומנט לתיקייה + ארגומנט לקובץ - Default Web Browser - The default setting follows the OS default browser setting. If specified separately, flow uses that browser. - Browser - Browser Name - Browser Path + דפדפן ברירת מחדל + ההגדרה המוגדרת כברירת מחדל עוקבת אחר הדפדפן המוגדר כברירת מחדל במערכת ההפעלה. אם צוין דפדפן אחר, Flow Launcher ישתמש בו. + דפדפן + שם דפדפן + נתיב דפדפן חלון חדש כרטיסייה חדשה מצב פרטיות - Change Priority - Greater the number, the higher the result will be ranked. Try setting it as 5. If you want the results to be lower than any other plugin's, provide a negative number - Please provide an valid integer for Priority! + שנה עדיפות + ככל שהמספר גבוה יותר, התוצאה תדורג גבוה יותר ברשימת החיפוש. נסה להגדיר את הערך ל-5. אם ברצונך שהתוצאות יופיעו אחרי תוצאות של כל תוסף אחר, הזן מספר שלילי. + אנא הזן מספר שלם תקף עבור העדיפות! - Old Action Keyword - New Action Keyword + מילת פעולה ישנה + מילת פעולה חדשה ביטול בוצע - Can't find specified plugin - New Action Keyword can't be empty - This new Action Keyword is already assigned to another plugin, please choose a different one + לא ניתן למצוא את התוסף שצוין + מילת הפעולה החדשה לא יכולה להיות ריקה + מילת הפעולה החדשה כבר מוקצה לתוסף אחר, אנא בחר אחת שונה הצליח הושלם בהצלחה - Enter the action keyword you like to use to start the plugin. Use * if you don't want to specify any, and the plugin will be triggered without any action keywords. + הזן את מילת הפעולה שברצונך להשתמש בה להפעלת התוסף. השתמש ב-* אם אינך רוצה לציין מילה כלשהי, והתוסף יופעל ללא צורך במילת פעולה. - Custom Query Hotkey - Press a custom hotkey to open Flow Launcher and input the specified query automatically. + מקש קיצור לשאילתה מותאמת אישית + הקש על מקש קיצור מותאם אישית כדי לפתוח את Flow Launcher ולהזין את השאילתה שצוינה באופן אוטומטי. תצוגה מקדימה - Hotkey is unavailable, please select a new hotkey - Invalid plugin hotkey + מקש הקיצור אינו זמין, אנא בחר מקש קיצור חדש + מקש קיצור לא חוקי לתוסף עדכון - Binding Hotkey - Current hotkey is unavailable. - This hotkey is reserved for "{0}" and can't be used. Please choose another hotkey. - This hotkey is already in use by "{0}". If you press "Overwrite", it will be removed from "{0}". - Press the keys you want to use for this function. + שיוך מקש קיצור + מקש הקיצור הנוכחי אינו זמין. + מקש קיצור זה שמור עבור "{0}" ואינו ניתן לשימוש. אנא בחר מקש קיצור אחר. + מקש קיצור זה כבר נמצא בשימוש על ידי "{0}". אם תלחץ על "החלף", הוא יוסר מ-"{0}". + הקש על המקשים שברצונך להשתמש בהם עבור פעולה זו. - Custom Query Shortcut - Enter a shortcut that automatically expands to the specified query. - A shortcut is expanded when it exactly matches the query. + קיצור דרך לשאילתה מותאמת אישית + הזן קיצור דרך שיוחלף אוטומטית בשאילתה שצוינה. + קיצור דרך מוחלף כאשר הוא תואם בדיוק לשאילתה. -If you add an '@' prefix while inputting a shortcut, it matches any position in the query. Builtin shortcuts match any position in a query. +אם תוסיף תחילית '@' בעת הזנת קיצור דרך, הוא יתאים לכל מיקום בשאילתה. קיצורי דרך מובנים מתאימים לכל מיקום בשאילתה. - Shortcut already exists, please enter a new Shortcut or edit the existing one. - Shortcut and/or its expansion is empty. + קיצור דרך כבר קיים, אנא הזן קיצור דרך חדש או ערוך את הקיים. + קיצור הדרך ו/או ההרחבה שלו ריקים. שמור - Overwrite + שכתב ביטול אפס מחק אישור כן לא + רקע גרסה זמן - Please tell us how application crashed so we can fix it + אנא תאר כיצד האפליקציה קרסה כדי שנוכל לתקן זאת שלח דיווח ביטול כללי חריגים - Exception Type + סוג החריגה מקור - Stack Trace + מעקב מחסנית שולח - Report sent successfully - Failed to send report - Flow Launcher got an error + הדוח נשלח בהצלחה + שליחת הדוח נכשלה + אירעה שגיאה ב-Flow Launcher + אנא פתח דיווח חדש ב + 1. העלה קובץ יומן: {0} + 2. העתק את הודעת החריגה למטה אנא המתן... - Checking for new update - You already have the latest Flow Launcher version + בודק עדכון חדש + כבר מותקנת אצלך הגרסה העדכנית של Flow Launcher עדכון נמצא מעדכן... - Flow Launcher was not able to move your user profile data to the new update version. - Please manually move your profile data folder from {0} to {1} + Flow Launcher לא הצליח להעביר את נתוני פרופיל המשתמש שלך לגרסת העדכון החדשה. + אנא העבר ידנית את תיקיית נתוני הפרופיל שלך מ-{0} אל-{1} עדכון חדש - New Flow Launcher release {0} is now available - An error occurred while trying to install software updates + גרסה חדשה {0} של Flow Launcher זמינה כעת + אירעה שגיאה במהלך ניסיון התקנת עדכוני התוכנה עדכון ביטול העדכון נכשל - Check your connection and try updating proxy settings to github-cloud.s3.amazonaws.com. - This upgrade will restart Flow Launcher - Following files will be updated + בדוק את החיבור שלך ונסה לעדכן את הגדרות הפרוקסי ל-github-cloud.s3.amazonaws.com. + שדרוג זה יאתחל את Flow Launcher + הקבצים הבאים יעודכנו עדכן קבצים - Update description + עדכן תיאור דלג - Welcome to Flow Launcher - Hello, this is the first time you are running Flow Launcher! - Before starting, this wizard will assist in setting up Flow Launcher. You can skip this if you wish. Please choose a language - Search and run all files and applications on your PC - Search everything from applications, files, bookmarks, YouTube, Twitter and more. All from the comfort of your keyboard without ever touching the mouse. - Flow Launcher starts with the hotkey below, go ahead and try it out now. To change it, click on the input and press the desired hotkey on the keyboard. + ברוך הבא אל Flow Launcher + שלום, זו הפעם הראשונה שבה Flow Launcher מופעל! + לפני שתתחיל, אשף זה יסייע בהגדרת Flow Launcher. אתה יכול לדלג על שלב זה. בחר שפה + חפש והפעל את כל הקבצים והיישומים במחשב שלך + חפש הכל מיישומים, קבצים, סימניות, YouTube, ועד טוויטר ועוד. הכל מהנוחות של המקלדת מבלי לגעת בעכבר. + ניתן להפעיל את Flow Launcherבקיצור המקש שלמטה, קדימה נסה אותו כעת! כדי לשנות אותו, לחץ על מקש הקיצור הרצוי במקלדת. מקשי קיצור - Action Keyword and Commands - Search the web, launch applications or run various functions through Flow Launcher plugins. Certain functions start with an action keyword, and if necessary, they can be used without action keywords. Try the queries below in Flow Launcher. - Let's Start Flow Launcher - Finished. Enjoy Flow Launcher. Don't forget the hotkey to start :) + מילת מפתח ופקודות פעולה + חפש באינטרנט, הפעל אפליקציות או הפעל פונקציות שונות באמצעות תוספים של Flow Launcher. פונקציות מסוימות מתחילות במילת מפתח פעולה, ובמידת הצורך, ניתן להשתמש בהן ללא מילות מפתח פעולה. נסה את השאילתות למטה ב-Flow Launcher. + בואו נתחיל עם Flow Launcher + סיימנו. תהנה מ-Flow Launcher. אל תשכח את מקש הקיצור כדי להתחיל :) - Back / Context Menu - Item Navigation - Open Context Menu - Open Containing Folder - Run as Admin / Open Folder in Default File Manager - Query History - Back to Result in Context Menu - Autocomplete - Open / Run Selected Item - Open Setting Window - Reload Plugin Data + חזור / תפריט הקשר + ניווט בין פריטים + פתח תפריט הקשר + פתח תיקייה מכילה + הפעל כמנהל / פתח תיקייה במנהל הקבצים ברירת מחדל + היסטוריית שאילתות + חזור לתוצאה בתפריט הקשר + השלמה אוטומטית + פתח / הפעל פריט נבחר + פתח חלון הגדרות + טען מחדש נתוני תוסף - Select first result - Select last result - Run current query again - Open result - Open result #{0} + בחר בתוצאה הראשונה + בחר בתוצאה האחרונה + הפעל מחדש את השאילתה הנוכחית + פתח תוצאה + פתח תוצאה #{0} - Weather - Weather in Google Result + מזג אוויר + מזג אוויר מתוצאות Google > ping 8.8.8.8 - Shell Command + פקודת Shell s Bluetooth - Bluetooth in Windows Settings + Bluetooth בהגדרות Windows sn - Sticky Notes + פתקים נדבקים - File Size - Created - Last Modified + גודל קובץ + נוצר + תאריך שינוי אחרון diff --git a/Flow.Launcher/Languages/it.xaml b/Flow.Launcher/Languages/it.xaml index 372f4630e..e4d4d3e2c 100644 --- a/Flow.Launcher/Languages/it.xaml +++ b/Flow.Launcher/Languages/it.xaml @@ -13,6 +13,7 @@ Registrazione del tasto di scelta rapida "{0}" non riuscita. Il tasto di scelta rapida potrebbe essere in uso da un altro programma. Passa a un altro tasto di scelta rapida o esci da un altro programma. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher Avvio fallito {0} Formato file plugin non valido @@ -44,6 +45,8 @@ Modalità portatile Memorizzare tutte le impostazioni e i dati dell'utente in un'unica cartella (utile se utilizzato con unità rimovibili o servizi cloud). Avvia Wow all'avvio di Windows + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Errore nell'impostazione del lancio all'avvio Nascondi Flow Launcher quando perde il focus Non mostrare le notifiche per una nuova versione @@ -126,7 +129,8 @@ Versione Sito Web Disinstalla - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Negozio dei Plugin @@ -143,8 +147,6 @@ Questo plugin è stato aggiornato negli ultimi 7 giorni Nuovo aggiornamento disponibile - - Tema Aspetto @@ -194,7 +196,6 @@ Questo tema supporta due (chiaro/scuro) varianti. Questo tema supporta lo sfondo trasparente blurrato. - Tasti scelta rapida Tasti scelta rapida @@ -297,6 +298,9 @@ Posizione Dati Utente Le impostazioni dell'utente e i plugin installati sono salvati nella cartella dati utente. Questa posizione può variare se è in modalità portable o no. Apri Cartella + Log Level + Debug + Info Seleziona Gestore File @@ -367,6 +371,7 @@ Se si aggiunge un prefisso '@' mentre si inserisce una scorciatoia, corrisponde OK No + Sfondo Versione @@ -383,6 +388,9 @@ Se si aggiunge un prefisso '@' mentre si inserisce una scorciatoia, corrisponde Rapporto inviato correttamente Invio rapporto fallito Flow Launcher ha riportato un errore + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Attendere prego... diff --git a/Flow.Launcher/Languages/ja.xaml b/Flow.Launcher/Languages/ja.xaml index e7671367a..28c334667 100644 --- a/Flow.Launcher/Languages/ja.xaml +++ b/Flow.Launcher/Languages/ja.xaml @@ -13,6 +13,7 @@ ホットキー "{0}" の登録に失敗しました。このホットキーは別のプログラムで使用されている可能性があります。別のホットキーに変更するか、このホットキーを使用しているプログラムを終了してください。 + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher {0}の起動に失敗しました Flow Launcherプラグインの形式が正しくありません @@ -44,6 +45,8 @@ ポータブルモード すべての設定とユーザーデータを1つのフォルダに保存します(リムーバブルドライブやクラウドサービスで使用する場合に便利です)。 スタートアップ時にFlow Launcherを起動する + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Error setting launch on startup フォーカスを失った時にFlow Launcherを隠す 最新版が入手可能であっても、アップグレードメッセージを表示しない @@ -126,7 +129,8 @@ バージョン ウェブサイト アンインストール - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually プラグインストア @@ -143,8 +147,6 @@ This plugin has been updated within the last 7 days 新しいアップデートが利用可能です - - テーマ 外観 @@ -194,7 +196,6 @@ This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. - ホットキー ホットキー @@ -297,6 +298,9 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Log Level + Debug + Info Select File Manager @@ -367,6 +371,7 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in Update Yes No + バックグラウンド バージョン @@ -383,6 +388,9 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in クラッシュレポートの送信に成功しました クラッシュレポートの送信に失敗しました Flow Launcherにエラーが発生しました + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Please wait... diff --git a/Flow.Launcher/Languages/ko.xaml b/Flow.Launcher/Languages/ko.xaml index 67bf2490a..d9da26bcb 100644 --- a/Flow.Launcher/Languages/ko.xaml +++ b/Flow.Launcher/Languages/ko.xaml @@ -13,6 +13,7 @@ Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher {0}을 실행할 수 없습니다. Flow Launcher 플러그인 파일 형식이 유효하지 않습니다. @@ -44,6 +45,8 @@ 포터블 모드 모든 설정이 폴더안에 들어갑니다. USB 드라이브나 클라우드로 사용 가능합니다. 시스템 시작 시 Flow Launcher 실행 + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Error setting launch on startup 포커스 잃으면 Flow Launcher 숨김 새 버전 알림 끄기 @@ -126,7 +129,8 @@ 버전 웹사이트 제거 - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually 플러그인 스토어 @@ -143,8 +147,6 @@ 이 플러그인은 최근 7일 사이 업데이트 되었습니다 새 업데이트 설치 가능 - - 테마 외관 @@ -194,7 +196,6 @@ This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. - 단축키 단축키 @@ -297,6 +298,9 @@ 사용자 데이터 위치 사용자 설정과 설치된 플러그인은 사용자 데이터 폴더에 저장됩니다. 이 위치는 휴대용 모드 활성화 여부에 따라 달라질 수 있습니다. 폴더 열기 + Log Level + Debug + Info 파일관리자 선택 @@ -367,6 +371,7 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in 확인 Yes No + 배경 버전 @@ -383,6 +388,9 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in 보고서를 정상적으로 보냈습니다. 보고서를 보내지 못했습니다. Flow Launcher에 문제가 발생했습니다. + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message 잠시 기다려주세요... diff --git a/Flow.Launcher/Languages/nb.xaml b/Flow.Launcher/Languages/nb.xaml index e3b8e36da..a37a204e1 100644 --- a/Flow.Launcher/Languages/nb.xaml +++ b/Flow.Launcher/Languages/nb.xaml @@ -13,6 +13,7 @@ Kan ikke registrere hurtigtasten "{0}". Hurtigtasten kan være i bruk av et annet program. Endre til en annen hurtigtast, eller avslutt et annet program. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher Kunne ikke starte {0} Ugyldig Flow Launcher programtillegg filformat @@ -44,6 +45,8 @@ Portabel modus Lagre alle innstillinger og brukerdata i en mappe (nyttig når man bruker flyttbare stasjoner eller skytjenester). Start Flow Launcher ved systemoppstart + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Feil ved å sette kjør ved oppstart Skjul Flow Launcher når fokus forsvinner Ikke vis varsler om nye versjoner @@ -126,7 +129,8 @@ Versjon Nettsted Avinstaller - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Programtillegg butikk @@ -143,8 +147,6 @@ Dette programtillegget er oppdatert i løpet av de siste 7 dagene Ny oppdatering er tilgjengelig - - Drakt Utseende @@ -194,7 +196,6 @@ Dette temaet støtter to (lys/mørk) moduser. Dette temaet støtter uskarp gjennomsiktig bakgrunn. - Hurtigtast Hurtigtaster @@ -297,6 +298,9 @@ Plassering av brukerdata Brukerinnstillinger og installerte programtillegg lagres i brukerens datamappe. Denne plasseringen kan variere avhengig av om den er i portabel modus eller ikke. Åpne mappe + Log Level + Debug + Info Velg filbehandler @@ -367,6 +371,7 @@ Hvis du legger til et @-prefiks mens du legger inn en snarvei, samsvarer det med OK Ja Nei + Bakgrunn Versjon @@ -383,6 +388,9 @@ Hvis du legger til et @-prefiks mens du legger inn en snarvei, samsvarer det med Rapporten ble sendt Kunne ikke sende rapport Flow Launcher fikk en feil + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Vennligst vent... diff --git a/Flow.Launcher/Languages/nl.xaml b/Flow.Launcher/Languages/nl.xaml index db440c3a0..64adccd94 100644 --- a/Flow.Launcher/Languages/nl.xaml +++ b/Flow.Launcher/Languages/nl.xaml @@ -13,6 +13,7 @@ Sneltoets "{0}" registreren. De sneltoets kan in gebruik zijn door een ander programma. Verander naar een andere sneltoets of sluit een ander programma. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher Kan {0} niet starten Ongeldige Flow Launcher plugin bestandsextensie @@ -44,6 +45,8 @@ Draagbare Modus Alle instellingen en gebruikersgegevens opslaan in één map (Nuttig bij het gebruik van verwijderbare schijven of cloud services). Start Flow Launcher als systeem opstart + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Fout bij het instellen van uitvoeren bij opstarten Verberg Flow Launcher als focus verloren is Laat geen nieuwe versie notificaties zien @@ -126,7 +129,8 @@ Versie Website Verwijderen - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Plugin Winkel @@ -143,8 +147,6 @@ Deze plug-in is in de laatste 7 dagen bijgewerkt Nieuwe update beschikbaar - - Thema Uiterlijk @@ -194,7 +196,6 @@ Dit thema ondersteunt twee (licht/donker) modi. This theme supports Blur Transparent Background. - Sneltoets Sneltoets @@ -297,6 +298,9 @@ Gegevenslocatie van gebruiker Gebruikersinstellingen en geïnstalleerde plug-ins worden opgeslagen in de gebruikersgegevensmap. Deze locatie kan variëren afhankelijk van of het in draagbare modus is of niet. Map openen + Log Level + Debug + Info Bestandsbeheerder selecteren @@ -367,6 +371,7 @@ Als u een '@' voorvoegsel toevoegt tijdens het invoeren van een snelkoppeling, m OK Yes No + Background Versie @@ -383,6 +388,9 @@ Als u een '@' voorvoegsel toevoegt tijdens het invoeren van een snelkoppeling, m Rapport succesvol verzonden Verzenden van rapport mislukt Flow Launcher heeft een error + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Please wait... diff --git a/Flow.Launcher/Languages/pl.xaml b/Flow.Launcher/Languages/pl.xaml index df7c9d2d4..06204395c 100644 --- a/Flow.Launcher/Languages/pl.xaml +++ b/Flow.Launcher/Languages/pl.xaml @@ -6,13 +6,14 @@ {2}{2} Kliknij "nie", jeśli jest już zainstalowany. Zostaniesz wtedy poproszony o wybranie folderu zawierającego plik wykonywalny {1} - Proszę wybrać plik wykonywalny {0} - Nie można ustawić ścieżki pliku wykonywalnego {0}, proszę spróbować z poziomu ustawień Flow (przewiń na dół strony). + Wybierz plik wykonywalny {0} + Nie można ustawić ścieżki do pliku wykonywalnego {0}. Spróbuj ponownie w ustawieniach Flow (przewiń na sam dół). Nie udało się zainicjować wtyczek - Wtyczki: {0} - nie udało się załadować i zostaną wyłączone, proszę skontaktować się z twórcą wtyczki w celu uzyskania pomocy + Wtyczki: {0} – nie udało się ich wczytać i zostaną wyłączone. Skontaktuj się z twórcą wtyczki, aby uzyskać pomoc - Nie udało się zarejestrować skrótu klawiszowego "{0}". Klucz skrótu może być używany przez inny program. Zmień skrót klawiszowy lub wyjdź z innego programu. + Nie udało się zarejestrować skrótu klawiszowego „{0}”. Skrót może być używany przez inny program. Zmień skrót na inny lub zamknij program, który go używa. + Nie udało się wyrejestrować skrótu „{0}”. Spróbuj ponownie lub sprawdź szczegóły w dzienniku Flow Launcher Nie udało się uruchomić: {0} Niepoprawny format pliku wtyczki @@ -44,6 +45,8 @@ Kliknij "nie", jeśli jest już zainstalowany. Zostaniesz wtedy popros Tryb przenośny Przechowuj wszystkie ustawienia i dane użytkownika w jednym folderze (Przydatne, gdy używane na dyskach wymiennych lub usługach chmurowych). Uruchamiaj Flow Launcher przy starcie systemu + Użyj zadania logowania zamiast wpisu autostartu, aby przyspieszyć uruchamianie + Po odinstalowaniu musisz ręcznie usunąć to zadanie (Flow.Launcher Startup) za pomocą Harmonogramu zadań Błąd uruchamiania ustawień przy starcie Ukryj okno Flow Launcher kiedy przestanie ono być aktywne Nie pokazuj powiadomienia o nowej wersji @@ -65,8 +68,8 @@ Kliknij "nie", jeśli jest już zainstalowany. Zostaniesz wtedy popros Zachowaj ostatnie zapytanie Wybierz ostatnie zapytanie Puste ostatnie zapytanie - Preserve Last Action Keyword - Select Last Action Keyword + Zachowaj ostatnie słowo kluczowe akcji + Wybierz ostatnie słowo kluczowe akcji Stała wysokość okna Wysokość okna nie jest regulowana poprzez przeciąganie. Maksymalna liczba wyników @@ -126,7 +129,8 @@ Kliknij "nie", jeśli jest już zainstalowany. Zostaniesz wtedy popros Wersja Strona Odinstalowywanie - + Nie udało się usunąć ustawień wtyczki + Wtyczki: {0} – nie udało się usunąć plików ustawień wtyczek, usuń je ręcznie Sklep z wtyczkami @@ -143,8 +147,6 @@ Kliknij "nie", jeśli jest już zainstalowany. Zostaniesz wtedy popros Ta wtyczka została zaktualizowana w ciągu ostatnich 7 dni Dostępna jest nowa aktualizacja - - Skórka Wygląd @@ -194,7 +196,6 @@ Kliknij "nie", jeśli jest już zainstalowany. Zostaniesz wtedy popros Ten motyw obsługuje dwa tryby (jasny/ciemny). Ten motyw obsługuje rozmyte przezroczyste tło. - Skrót klawiszowy Skrót klawiszowy @@ -297,6 +298,9 @@ Kliknij "nie", jeśli jest już zainstalowany. Zostaniesz wtedy popros Lokalizacja danych użytkownika Ustawienia użytkownika i zainstalowane wtyczki są zapisywane w folderze danych użytkownika. Ta lokalizacja może się różnić w zależności od tego, czy aplikacja jest w trybie przenośnym, czy nie. Otwórz folder + Log Level + Debug + Info Wybierz menedżer plików @@ -367,6 +371,7 @@ Jeśli dodasz prefiks '@' podczas wprowadzania skrótu, będzie on pasował do d Aktualizuj Tak Nie + Tło Wersja @@ -383,6 +388,9 @@ Jeśli dodasz prefiks '@' podczas wprowadzania skrótu, będzie on pasował do d Raport wysłany pomyślnie Nie udało się wysłać raportu W programie Flow Launcher wystąpił błąd + Otwórz nowe zgłoszenie w + 1. Prześlij plik dziennika: {0} + 2. Skopiuj poniższą wiadomość wyjątku Proszę czekać... @@ -413,8 +421,8 @@ Jeśli dodasz prefiks '@' podczas wprowadzania skrótu, będzie on pasował do d Witamy w Flow Launcher Witaj, po raz pierwszy uruchamiasz Flow Launcher! Przed rozpoczęciem ten kreator pomoże skonfigurować Flow Launcher. Jeśli chcesz, możesz to pominąć. Proszę wybierz język - Wyszukiwanie i uruchamianie wszystkich plików i aplikacji na PC - Przeszukuj wszystko, od aplikacji, plików, zakładek, YouTube, X i nie tylko. Wszystko to z komfortowej klawiatury, bez konieczności dotykania myszy. + Wyszukuj i uruchamiaj pliki oraz aplikacje na komputerze + Wyszukuj wszystko – aplikacje, pliki, zakładki, YouTube, Twitter i nie tylko. Wszystko wygodnie z klawiatury, bez używania myszy. Flow Launcher uruchamia się za pomocą poniższego skrótu klawiszowego, śmiało i wypróbuj go teraz. Aby to zmienić, kliknij dane wejściowe i naciśnij żądany klawisz skrótu na klawiaturze. Skróty klawiszowe Słowo kluczowe akcji i polecenia @@ -425,7 +433,7 @@ Jeśli dodasz prefiks '@' podczas wprowadzania skrótu, będzie on pasował do d Powrót / Menu kontekstowe - Nawigacja pozycji + Nawigacja po elementach Otwórz menu kontekstowe Otwórz folder zawierający Uruchom jako administrator / Otwórz folder w domyślnym menedżerze plików diff --git a/Flow.Launcher/Languages/pt-br.xaml b/Flow.Launcher/Languages/pt-br.xaml index 4a30ffda4..62293b1a1 100644 --- a/Flow.Launcher/Languages/pt-br.xaml +++ b/Flow.Launcher/Languages/pt-br.xaml @@ -13,6 +13,7 @@ Falha em registrar a tecla de atalho "{0}". A combinação pode estar em uso por outro programa. Mude para uma tecla de atalho diferente, ou encerre o outro programa. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher Não foi possível iniciar {0} Formato de plugin Flow Launcher inválido @@ -44,6 +45,8 @@ Modo Portátil Armazene todas as configurações e dados do usuário em uma pasta (útil quando usado com unidades removíveis ou serviços em nuvem). Iniciar Flow Launcher com inicialização do sistema + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Erro ao ativar início com o sistema Esconder Flow Launcher quando foco for perdido Não mostrar notificações de novas versões @@ -126,7 +129,8 @@ Versão Site Desinstalar - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Loja de Plugins @@ -143,8 +147,6 @@ Este plugin foi atualizado nos últimos 7 dias Nova Atualização Disponível - - Tema Aparência @@ -194,7 +196,6 @@ This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. - Atalho Atalho @@ -297,6 +298,9 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Log Level + Debug + Info Selecione o Gerenciador de Arquivos @@ -367,6 +371,7 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in OK Yes No + Plano de fundo Versão @@ -383,6 +388,9 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in Relatório enviado com sucesso Falha ao enviar relatório Flow Launcher apresentou um erro + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Por favor, aguarde... diff --git a/Flow.Launcher/Languages/pt-pt.xaml b/Flow.Launcher/Languages/pt-pt.xaml index 470bf2b4e..bad37f688 100644 --- a/Flow.Launcher/Languages/pt-pt.xaml +++ b/Flow.Launcher/Languages/pt-pt.xaml @@ -2,17 +2,18 @@ - Flow Launcher detetou que tem instalados {0} plugins e que necessitam de {1} para serem executados. Gostaria de descarregar {1}? -{2}{2} -Clique "Não" se já tiver instalado e, de seguida, ser-lhe-á solicitada a pasta que contém o executável {1}. + Flow Launcher detetou que tem instalou os plugins {0}, que necessitam de {1} para serem executados. Gostaria de descarregar {1}? + {2}{2} + Clique "Não" se já tiver instalado e, de seguida, ser-lhe-á solicitada a pasta que contém o executável {1}. Por favor, selecione o executável {0} Não foi possível definir o caminho do executável {0}. Experimente definir o caminho nas definições (desloque até ao fundo). Falha ao iniciar os plugins - Plugins: {0} - não foi possível iniciar e serão desativados. Contacte o criador dos plugin para obter ajuda. + Plugin: {0} - não foi possível iniciar e será desativado. Contacte o criador do plugin para obter ajuda. Falha ao registar a tecla de atalho "{0}". A tecla de atalho pode estar a ser usada por outra aplicação. Utilize uma tecla de atalho diferente ou feche o outro programa. + Falha ao cancelar a atribuição da tecla de atalho "{0}". Tente novamente ou consulte o registo para mais detalhes. Flow Launcher Não foi possível iniciar {0} Formato do ficheiro inválido como plugin @@ -44,6 +45,8 @@ Clique "Não" se já tiver instalado e, de seguida, ser-lhe-á solicit Modo portátil Guardar todas as definições e dados do utilizador numa pasta (indicado se utilizar discos amovíveis ou serviços cloud) Iniciar Flow Launcher ao arrancar o sistema + Utilizar tarefa de arranque em vez de uma entrada de arranque para uma experiência mais rápida + Se desinstalar a aplicação, tem que remover manualmente a tarefa (Flow.Launcher Startup) no agendamento de tarefas Erro ao definir para iniciar ao arrancar Ocultar Flow Launcher ao perder o foco Não notificar acerca de novas versões @@ -126,7 +129,8 @@ Clique "Não" se já tiver instalado e, de seguida, ser-lhe-á solicit Versão Site Desinstalar - + Falha ao remover as definições do plugin + Plugin: {0} - Falha ao remover o ficheiro de definições do plugin. Experimente remover manualmente. Loja de plugins @@ -143,8 +147,6 @@ Clique "Não" se já tiver instalado e, de seguida, ser-lhe-á solicit Este plugin foi atualizado nos últimos 7 dias Atualização disponível - - Tema Aparência @@ -194,7 +196,6 @@ Clique "Não" se já tiver instalado e, de seguida, ser-lhe-á solicit Este tema tem suporte a dois modos (claro/escuro). Este tema tem suporte a fundo transparente desfocado. - Tecla de atalho Teclas de atalho @@ -296,6 +297,9 @@ Clique "Não" se já tiver instalado e, de seguida, ser-lhe-á solicit Localização dos dados do utilizador As definições e os plugins instalados são guardados na pasta de dados do utilizador. A localização pode variar, tendo em conta se a aplicação está instalada ou no modo portátil Abrir pasta + Log Level + Debug + Info Selecione o gestor de ficheiros @@ -366,6 +370,7 @@ Se adicionar o prefixo '@' durante a introdução do atalho, será utilizada qua OK Sim Não + Fundo Versão @@ -382,6 +387,9 @@ Se adicionar o prefixo '@' durante a introdução do atalho, será utilizada qua Relatório enviado com sucesso Falha ao enviar o relatório Ocorreu um erro + Abra um relatório de erro em + 1. Carregue o ficheiro de registos: {0} + 2. Copie a mensagem abaixo Por favor aguarde... diff --git a/Flow.Launcher/Languages/ru.xaml b/Flow.Launcher/Languages/ru.xaml index 1c420100f..a56a770da 100644 --- a/Flow.Launcher/Languages/ru.xaml +++ b/Flow.Launcher/Languages/ru.xaml @@ -13,6 +13,7 @@ Не удалось зарегистрировать сочетание клавиш "{0}". Возможно, оно используется другой программой. Измените сочетание клавиш или закройте другую программу. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher Не удалось запустить {0} Недопустимый формат файла плагина Flow Launcher @@ -44,6 +45,8 @@ Портативный режим Храните все настройки и данные пользователя в одной папке (полезно при использовании со съёмными дисками или облачными сервисами). Запускать Flow Launcher при запуске системы + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Ошибка настройки запуска при запуске Скрывать Flow Launcher, если потерян фокуc Не отображать сообщение об обновлении, когда доступна новая версия @@ -126,7 +129,8 @@ Версия Веб-сайт Удалить - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Магазин плагинов @@ -143,8 +147,6 @@ Этот плагин был обновлён за последние 7 дней Доступно новое обновление - - Тема Внешний вид @@ -194,7 +196,6 @@ This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. - Горячая клавиша Горячая клавиша @@ -297,6 +298,9 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Log Level + Debug + Info Выбор менеджера файлов @@ -367,6 +371,7 @@ OK Yes No + Фон Версия @@ -383,6 +388,9 @@ Отчёт успешно отправлен Не удалось отправить отчёт Произошёл сбой в Flow Launcher + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Пожалуйста, подождите... diff --git a/Flow.Launcher/Languages/sk.xaml b/Flow.Launcher/Languages/sk.xaml index 3db778cc8..332528b2b 100644 --- a/Flow.Launcher/Languages/sk.xaml +++ b/Flow.Launcher/Languages/sk.xaml @@ -13,6 +13,7 @@ Nepodarilo sa zaregistrovať klávesovú skratku "{0}". Klávesová skratka môže byť používaná iným programom. Zmeňte klávesovú skratku na inú alebo ukončite iný program. + Nepodarilo sa registrovať klávesovú skratku "{0}". Skúste to znova alebo si pozrite podrobnosti v denníku Flow Launcher Nepodarilo sa spustiť {0} Neplatný formát súboru pre plugin Flow Launchera @@ -44,6 +45,8 @@ Prenosný režim Uloží všetky nastavenia a používateľské údaje do jedného priečinka (Užitočné pri vymeniteľných diskoch a cloudových službách). Spustiť Flow Launcher pri spustení systému + Pre rýchlejšie spustenie použiť úlohu pri prihlásení namiesto položky po spustení + Po odinštalovaní musíte úlohu manuálne odstrániť (Flow.Launcher Startup) cez Plánovač úloh Chybné nastavenie spustenia pri spustení Schovať Flow Launcher po strate fokusu Nezobrazovať upozornenia na novú verziu @@ -126,7 +129,8 @@ Verzia Webstránka Odinštalovať - + Nepodarilo sa odstrániť nastavenia pluginu + Pluginy: {0} – Nepodarilo sa odstrániť súbory s nastaveniami pluginu, odstráňte ich manuálne Repozitár pluginov @@ -143,8 +147,6 @@ Tento plugin bol aktualizovaný za posledných 7 dní K dispozícii je nová aktualizácia - - Motív Vzhľad @@ -194,7 +196,6 @@ Tento motív podporuje 2 režimy (svetlý/tmavý). Tento motív podporuje rozostrenie priehľadného pozadia. - Klávesové skratky Klávesové skratky @@ -297,6 +298,9 @@ Cesta k používateľskému priečinku Nastavenia používateľa a nainštalované pluginy sa ukladajú do používateľského priečinka. Toto umiestnenie sa môže líšiť v závislosti od toho, či je v prenosnom režime alebo nie. Otvoriť priečinok + Úroveň logovania + Debug + Info Vyberte správcu súborov @@ -367,6 +371,7 @@ Ak pri zadávaní skratky pred ňu pridáte "@", bude sa zhodovať s Aktualizovať Áno Nie + Pozadie Verzia @@ -383,6 +388,9 @@ Ak pri zadávaní skratky pred ňu pridáte "@", bude sa zhodovať s Hlásenie bolo úspešne odoslané Odoslanie hlásenia zlyhalo Flow Launcher zaznamenal chybu + Prosím, otvorte nové issue na + 1. Nahrajte súbor logu: {0} + 2. Skopírujte nižšie uvedenú správu o výnimke Čakajte, prosím... diff --git a/Flow.Launcher/Languages/sr.xaml b/Flow.Launcher/Languages/sr.xaml index fc5a1e2fc..b244c4660 100644 --- a/Flow.Launcher/Languages/sr.xaml +++ b/Flow.Launcher/Languages/sr.xaml @@ -13,6 +13,7 @@ Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher Neuspešno pokretanje {0} Nepravilni Flow Launcher plugin format datoteke @@ -44,6 +45,8 @@ Portable Mode Store all settings and user data in one folder (Useful when used with removable drives or cloud services). Pokreni Flow Launcher pri podizanju sistema + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Error setting launch on startup Sakri Flow Launcher kada se izgubi fokus Ne prikazuj obaveštenje o novoj verziji @@ -126,7 +129,8 @@ Verzija Website Uninstall - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Plugin Store @@ -143,8 +147,6 @@ This plugin has been updated within the last 7 days New Update is Available - - Tema Appearance @@ -194,7 +196,6 @@ This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. - Prečica Prečica @@ -297,6 +298,9 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Log Level + Debug + Info Select File Manager @@ -367,6 +371,7 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in OK Yes No + Background Verzija @@ -383,6 +388,9 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in Izveštaj uspešno poslat Izveštaj neuspešno poslat Flow Launcher je dobio grešku + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Please wait... diff --git a/Flow.Launcher/Languages/tr.xaml b/Flow.Launcher/Languages/tr.xaml index cfb3689c3..5d550ebed 100644 --- a/Flow.Launcher/Languages/tr.xaml +++ b/Flow.Launcher/Languages/tr.xaml @@ -13,6 +13,7 @@ "{0}" kısayolunu atama başarısız oldu. Kısayolu başka bir program kullanıyorsa kapatmayı deneyin veya kısayolu değiştirin. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher {0} başlatılamıyor Geçersiz Flow Launcher eklenti dosyası formatı @@ -44,6 +45,8 @@ Taşınabilir Mod Tüm ayarları ve kullanıcı verilerini tek bir klasörde saklayın (Çıkarılabilir sürücüler veya bulut hizmetleri ile kullanıldığında kullanışlıdır). Sistem ile Başlat + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Sistemle başlatma ayarı başarısız oldu Odak Pencereden Ayrıldığında Gizle Güncelleme bildirimlerini gösterme @@ -126,7 +129,8 @@ Sürüm İnternet Sitesi Kaldır - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Eklenti Mağazası @@ -143,8 +147,6 @@ Bu eklenti son 7 gün içerisinde güncellenmiş. Yeni Bir Güncelleme Mevcut - - Temalar Görünüm @@ -194,7 +196,6 @@ This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. - Kısayol Tuşu Kısayol Tuşu @@ -297,6 +298,9 @@ Kullanıcı Verisi Dizini Kullanıcı ayarları ve yüklü eklentiler bu klasörde saklanır. Klasörün konumu taşınabilir moda bağlı olarak değişebilir. Klasörü Aç + Log Level + Debug + Info Dosya Yöneticisi Seçenekleri @@ -365,6 +369,7 @@ Güncelle Yes No + Arka plan Sürüm @@ -381,6 +386,9 @@ Hata raporu başarıyla gönderildi Hata raporu gönderimi başarısız oldu Flow Launcher'da bir hata oluştu + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Lütfen bekleyin... diff --git a/Flow.Launcher/Languages/uk-UA.xaml b/Flow.Launcher/Languages/uk-UA.xaml index 09c576150..95a746a51 100644 --- a/Flow.Launcher/Languages/uk-UA.xaml +++ b/Flow.Launcher/Languages/uk-UA.xaml @@ -13,6 +13,7 @@ Не вдалося зареєструвати гарячу клавішу "{0}". Можливо, гаряча клавіша використовується іншою програмою. Змініть її на іншу гарячу клавішу або вийдіть з програми, де вона використовується. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher Не вдалося запустити {0} Невірний формат файлу плагіна Flow Launcher @@ -44,6 +45,8 @@ Портативний режим Зберігати всі налаштування і дані користувача в одній теці (буде корисно при видаленні дисків або хмарних сервісах). Запускати Flow Launcher при запуску системи + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Помилка запуску налаштування під час запуску Сховати Flow Launcher, якщо втрачено фокус Не повідомляти про доступні нові версії @@ -126,7 +129,8 @@ Версія Сайт Видалити - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Магазин плагінів @@ -143,8 +147,6 @@ Цей плагін було оновлено протягом останніх 7 днів Доступне нове оновлення - - Тема Зовнішній вигляд @@ -194,7 +196,6 @@ This theme supports two(light/dark) modes. Ця тема підтримує розмитий прозорий фон. - Гаряча клавіша Гарячі клавіші @@ -297,6 +298,9 @@ Розташування даних користувача Налаштування користувача та встановлені плагіни зберігаються у теці даних користувача. Це місце може змінюватися залежно від того, чи перебуває програма в портативному режимі, чи ні. Відкрити теку + Log Level + Debug + Info Виберіть файловий менеджер @@ -367,6 +371,7 @@ Добре Так Ні + Тло Версія @@ -383,6 +388,9 @@ Звіт успішно відправлено Не вдалося відправити звіт Стався збій в додатку Flow Launcher + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Будь ласка, зачекайте... diff --git a/Flow.Launcher/Languages/vi.xaml b/Flow.Launcher/Languages/vi.xaml index 31e8ad2aa..17e421e16 100644 --- a/Flow.Launcher/Languages/vi.xaml +++ b/Flow.Launcher/Languages/vi.xaml @@ -13,6 +13,7 @@ Không thể đăng ký phím nóng "{0}". Phím nóng có thể được sử dụng bởi một chương trình khác. Chuyển sang phím nóng khác hoặc thoát khỏi chương trình khác. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher Không thể khởi động {0} Định dạng tệp plugin Flow Launcher không chính xác @@ -44,6 +45,8 @@ Chế độ Portabler Lưu trữ tất cả cài đặt và dữ liệu người dùng trong một thư mục (hữu ích khi sử dụng với thiết bị lưu trữ di động hoặc dịch vụ đám mây). Khởi động Flow Launcher khi khởi động hệ thống + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Không lưu được tính năng tự khởi động khi khởi động hệ thống Ẩn Flow Launcher khi mất tiêu điểm Không hiển thị thông báo khi có phiên bản mới @@ -126,7 +129,8 @@ Phiên bản Trang web Gỡ cài đặt - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually Tải tiện ích mở rộng @@ -143,8 +147,6 @@ Plugin này đã được cập nhật trong vòng 7 ngày qua Đã có bản cập nhật mới - - Giao Diện Giao diện @@ -194,7 +196,6 @@ This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. - Phím tắt Phím tắt @@ -299,6 +300,9 @@ Vị trí dữ liệu người dùng Thiết đặt người dùng và plugin đã cài đặt sẽ được lưu trong thư mục dữ liệu người dùng. Vị trí này có thể thay đổi tùy thuộc vào việc nó có ở chế độ di động hay không. Mở thư mục + Log Level + Debug + Info Chọn trình quản lý tệp @@ -371,6 +375,7 @@ OK Không + Nền Phiên bản @@ -387,6 +392,9 @@ Đã gửi báo cáo thành công Báo cáo lỗi Trình khởi chạy luồng có lỗi + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message Cảnh báo nhỏ... diff --git a/Flow.Launcher/Languages/zh-cn.xaml b/Flow.Launcher/Languages/zh-cn.xaml index 681c715fb..d9966d757 100644 --- a/Flow.Launcher/Languages/zh-cn.xaml +++ b/Flow.Launcher/Languages/zh-cn.xaml @@ -13,6 +13,7 @@ 无法注册热键“{0}”。该热键可能正在被其他程序使用。更改为不同的热键,或退出其他程序。 + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher 启动命令 {0} 失败 无效的 Flow Launcher 插件文件格式 @@ -44,6 +45,8 @@ 便携模式 将所有设置和用户数据存储在一个文件夹中 (可用于可移除驱动器或云服务)。 开机自启 + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler 设置开机自启时出错 失去焦点时自动隐藏 Flow Launcher 不显示新版本提示 @@ -126,7 +129,8 @@ 版本 官方网站 卸载 - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually 插件商店 @@ -143,8 +147,6 @@ 此插件在过去7天内有更新 有可用的更新 - - 主题 外观 @@ -194,7 +196,6 @@ 该主题支持两种(浅色/深色)模式。 该主题支持模糊透明背景。 - 热键 热键 @@ -297,6 +298,9 @@ 用户数据位置 用户设置和已安装的插件保存在用户数据文件夹中。此位置可能因是否处于便携模式而异。 打开文件夹 + Log Level + Debug + Info 默认文件管理器 @@ -367,6 +371,7 @@ 更新 + 背景 版本 @@ -383,6 +388,9 @@ 发送成功 发送失败 Flow Launcher 出错啦 + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message 请稍等... diff --git a/Flow.Launcher/Languages/zh-tw.xaml b/Flow.Launcher/Languages/zh-tw.xaml index 44be5257b..01b667e72 100644 --- a/Flow.Launcher/Languages/zh-tw.xaml +++ b/Flow.Launcher/Languages/zh-tw.xaml @@ -13,6 +13,7 @@ Failed to register hotkey "{0}". The hotkey may be in use by another program. Change to a different hotkey, or exit another program. + Failed to unregister hotkey "{0}". Please try again or see log for details Flow Launcher 啟動命令 {0} 失敗 無效的 Flow Launcher 外掛格式 @@ -44,6 +45,8 @@ 便攜模式 將所有設定和使用者資料存儲在一個資料夾中(當與可移動磁碟或雲服務一起使用時很有用)。 開機時啟動 + Use logon task instead of startup entry for faster startup experience + After uninstallation, you need to manually remove this task (Flow.Launcher Startup) via Task Scheduler Error setting launch on startup 失去焦點時自動隱藏 Flow Launcher 不顯示新版本提示 @@ -126,7 +129,8 @@ 版本 官方網站 解除安裝 - + Fail to remove plugin settings + Plugins: {0} - Fail to remove plugin settings files, please remove them manually 插件商店 @@ -143,8 +147,6 @@ This plugin has been updated within the last 7 days New Update is Available - - 主題 外觀 @@ -194,7 +196,6 @@ This theme supports two(light/dark) modes. This theme supports Blur Transparent Background. - 快捷鍵 快捷鍵 @@ -297,6 +298,9 @@ User Data Location User settings and installed plugins are saved in the user data folder. This location may vary depending on whether it's in portable mode or not. Open Folder + Log Level + Debug + Info 選擇檔案管理器 @@ -367,6 +371,7 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in 更新 Yes No + 背景 版本 @@ -383,6 +388,9 @@ If you add an '@' prefix while inputting a shortcut, it matches any position in 傳送成功 傳送失敗 Flow Launcher 出錯啦 + Please open new issue in + 1. Upload log file: {0} + 2. Copy below exception message 請稍後... diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 10fc3583b..087b2e998 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -17,20 +17,21 @@ AllowDrop="True" AllowsTransparency="True" Background="Transparent" + Closed="OnClosed" Closing="OnClosing" Deactivated="OnDeactivated" Icon="Images/app.png" - SourceInitialized="OnSourceInitialized" - Initialized="OnInitialized" Left="{Binding Settings.WindowLeft, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Loaded="OnLoaded" LocationChanged="OnLocationChanged" Opacity="{Binding MainWindowOpacity, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" PreviewKeyDown="OnKeyDown" PreviewKeyUp="OnKeyUp" + PreviewMouseMove="OnPreviewMouseMove" ResizeMode="CanResize" ShowInTaskbar="False" SizeToContent="Height" + SourceInitialized="OnSourceInitialized" Topmost="True" Visibility="{Binding MainWindowVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" WindowStartupLocation="Manual" @@ -215,7 +216,7 @@ - + - + @@ -331,7 +332,6 @@ HorizontalAlignment="Center" VerticalAlignment="Bottom" StrokeThickness="2" - Style="{DynamicResource PendingLineStyle}" Visibility="{Binding ProgressBarVisibility, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" X1="-100" X2="0" @@ -339,7 +339,7 @@ Y2="0" /> - + + + + 70,13.5,18,13.5 + + 9,0,0,0 + 0,0,9,0 + 0,4.5,0,4.5 + + 9,4.5,0,4.5 + 0,4.5,9,4.5 + + + + 180 + 240 + 150 + + diff --git a/Flow.Launcher/Resources/Dark.xaml b/Flow.Launcher/Resources/Dark.xaml index ed031c939..25cc8e878 100644 --- a/Flow.Launcher/Resources/Dark.xaml +++ b/Flow.Launcher/Resources/Dark.xaml @@ -7,17 +7,17 @@ xmlns:sys="clr-namespace:System;assembly=mscorlib"> - + - - - - + + + + - - - + + + #198F8F8F diff --git a/Flow.Launcher/Resources/Light.xaml b/Flow.Launcher/Resources/Light.xaml index 8fe84588f..b9f65ce96 100644 --- a/Flow.Launcher/Resources/Light.xaml +++ b/Flow.Launcher/Resources/Light.xaml @@ -7,18 +7,18 @@ xmlns:sys="clr-namespace:System;assembly=mscorlib"> - + - - + + - - - - #198F8F8F + + + + #0C000000 diff --git a/Flow.Launcher/Resources/Pages/WelcomePage1.xaml.cs b/Flow.Launcher/Resources/Pages/WelcomePage1.xaml.cs index 98fb47288..880bfd9bc 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage1.xaml.cs +++ b/Flow.Launcher/Resources/Pages/WelcomePage1.xaml.cs @@ -1,8 +1,9 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Windows.Navigation; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Core.Resource; +using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.ViewModel; namespace Flow.Launcher.Resources.Pages { @@ -10,10 +11,10 @@ namespace Flow.Launcher.Resources.Pages { protected override void OnNavigatedTo(NavigationEventArgs e) { - if (e.ExtraData is Settings settings) - Settings = settings; - else - throw new ArgumentException("Unexpected Navigation Parameter for Settings"); + Settings = Ioc.Default.GetRequiredService(); + // Sometimes the navigation is not triggered by button click, + // so we need to reset the page number + Ioc.Default.GetRequiredService().PageNum = 1; InitializeComponent(); } private Internationalization _translater => InternationalizationManager.Instance; @@ -37,4 +38,4 @@ namespace Flow.Launcher.Resources.Pages } } -} \ No newline at end of file +} diff --git a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml index 6c6fcbb62..04c76d027 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml +++ b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml @@ -114,8 +114,7 @@ Margin="0,8,0,0" ChangeHotkey="{Binding SetTogglingHotkeyCommand}" DefaultHotkey="Alt+Space" - Hotkey="{Binding Settings.Hotkey}" - HotkeySettings="{Binding Settings}" + Type="Hotkey" ValidateKeyGesture="True" WindowTitle="{DynamicResource flowlauncherHotkey}" /> diff --git a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs index 1ed5747cd..786b4d506 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs +++ b/Flow.Launcher/Resources/Pages/WelcomePage2.xaml.cs @@ -1,11 +1,11 @@ using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Infrastructure.UserSettings; -using System; using System.Windows.Navigation; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.ViewModel; using System.Windows.Media; +using CommunityToolkit.Mvvm.DependencyInjection; namespace Flow.Launcher.Resources.Pages { @@ -15,11 +15,10 @@ namespace Flow.Launcher.Resources.Pages protected override void OnNavigatedTo(NavigationEventArgs e) { - if (e.ExtraData is Settings settings) - Settings = settings; - else - throw new ArgumentException("Unexpected Parameter setting."); - + Settings = Ioc.Default.GetRequiredService(); + // Sometimes the navigation is not triggered by button click, + // so we need to reset the page number + Ioc.Default.GetRequiredService().PageNum = 2; InitializeComponent(); } diff --git a/Flow.Launcher/Resources/Pages/WelcomePage3.xaml.cs b/Flow.Launcher/Resources/Pages/WelcomePage3.xaml.cs index 9051e7c27..f59b65c1c 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage3.xaml.cs +++ b/Flow.Launcher/Resources/Pages/WelcomePage3.xaml.cs @@ -1,6 +1,7 @@ -using System; -using System.Windows.Navigation; +using System.Windows.Navigation; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.ViewModel; namespace Flow.Launcher.Resources.Pages { @@ -8,10 +9,10 @@ namespace Flow.Launcher.Resources.Pages { protected override void OnNavigatedTo(NavigationEventArgs e) { - if (e.ExtraData is Settings settings) - Settings = settings; - else if(Settings is null) - throw new ArgumentException("Unexpected Navigation Parameter for Settings"); + Settings = Ioc.Default.GetRequiredService(); + // Sometimes the navigation is not triggered by button click, + // so we need to reset the page number + Ioc.Default.GetRequiredService().PageNum = 3; InitializeComponent(); } diff --git a/Flow.Launcher/Resources/Pages/WelcomePage4.xaml.cs b/Flow.Launcher/Resources/Pages/WelcomePage4.xaml.cs index 11bbcd6ed..4c83f3a83 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage4.xaml.cs +++ b/Flow.Launcher/Resources/Pages/WelcomePage4.xaml.cs @@ -1,5 +1,6 @@ -using Flow.Launcher.Infrastructure.UserSettings; -using System; +using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.ViewModel; using System.Windows.Navigation; namespace Flow.Launcher.Resources.Pages @@ -8,10 +9,10 @@ namespace Flow.Launcher.Resources.Pages { protected override void OnNavigatedTo(NavigationEventArgs e) { - if (e.ExtraData is Settings settings) - Settings = settings; - else - throw new ArgumentException("Unexpected Navigation Parameter for Settings"); + Settings = Ioc.Default.GetRequiredService(); + // Sometimes the navigation is not triggered by button click, + // so we need to reset the page number + Ioc.Default.GetRequiredService().PageNum = 4; InitializeComponent(); } diff --git a/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs b/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs index abb805303..95d7ff1a0 100644 --- a/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs +++ b/Flow.Launcher/Resources/Pages/WelcomePage5.xaml.cs @@ -1,9 +1,10 @@ -using System; -using System.Windows; +using System.Windows; using System.Windows.Navigation; using Flow.Launcher.Infrastructure.UserSettings; using Microsoft.Win32; using Flow.Launcher.Infrastructure; +using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.ViewModel; namespace Flow.Launcher.Resources.Pages { @@ -15,10 +16,10 @@ namespace Flow.Launcher.Resources.Pages protected override void OnNavigatedTo(NavigationEventArgs e) { - if (e.ExtraData is Settings settings) - Settings = settings; - else - throw new ArgumentException("Unexpected Navigation Parameter for Settings"); + Settings = Ioc.Default.GetRequiredService(); + // Sometimes the navigation is not triggered by button click, + // so we need to reset the page number + Ioc.Default.GetRequiredService().PageNum = 5; InitializeComponent(); } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs index 6b44be7c1..f82f8e34d 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneAboutViewModel.cs @@ -80,7 +80,7 @@ public partial class SettingsPaneAboutViewModel : BaseModel [RelayCommand] private void OpenWelcomeWindow() { - var window = new WelcomeWindow(_settings); + var window = new WelcomeWindow(); window.ShowDialog(); } diff --git a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs index ed933678d..647a9bcfc 100644 --- a/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs +++ b/Flow.Launcher/SettingPages/ViewModels/SettingsPaneThemeViewModel.cs @@ -5,6 +5,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Windows.Media; +using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Resource; using Flow.Launcher.Helper; @@ -13,7 +14,6 @@ using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.ViewModel; using ModernWpf; -using ThemeManager = Flow.Launcher.Core.Resource.ThemeManager; using ThemeManagerForColorSchemeSwitch = ModernWpf.ThemeManager; namespace Flow.Launcher.SettingPages.ViewModels; @@ -22,45 +22,68 @@ public partial class SettingsPaneThemeViewModel : BaseModel { private const string DefaultFont = "Segoe UI"; public Settings Settings { get; } + private readonly Theme _theme = Ioc.Default.GetRequiredService(); public static string LinkHowToCreateTheme => @"https://flowlauncher.com/docs/#/how-to-create-a-theme"; public static string LinkThemeGallery => "https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438"; + private List _themes; + public List Themes => _themes ??= _theme.LoadAvailableThemes(); + private Theme.ThemeData _selectedTheme; public Theme.ThemeData SelectedTheme { - get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == Settings.Theme); + get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == _theme.GetCurrentTheme()); set { _selectedTheme = value; - ThemeManager.Instance.ChangeTheme(value.FileNameWithoutExtension); + _theme.ChangeTheme(value.FileNameWithoutExtension); - if (ThemeManager.Instance.BlurEnabled && Settings.UseDropShadowEffect) - DropShadowEffect = false; + // Update UI state + OnPropertyChanged(nameof(BackdropType)); + OnPropertyChanged(nameof(IsBackdropEnabled)); + OnPropertyChanged(nameof(IsDropShadowEnabled)); + OnPropertyChanged(nameof(DropShadowEffect)); + + _ = _theme.RefreshFrameAsync(); } } + public bool IsBackdropEnabled + { + get + { + if (!Win32Helper.IsBackdropSupported()) return false; + return SelectedTheme?.HasBlur ?? false; + } + } + + public bool IsDropShadowEnabled => !_theme.BlurEnabled; + public bool DropShadowEffect { get => Settings.UseDropShadowEffect; set { - if (ThemeManager.Instance.BlurEnabled && value) + if (_theme.BlurEnabled) { - App.API.ShowMsgBox(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed")); + // Always DropShadowEffect = true with blur theme + Settings.UseDropShadowEffect = true; return; } + // User can change shadow with non-blur theme. if (value) { - ThemeManager.Instance.AddDropShadowEffectToCurrentTheme(); + _theme.AddDropShadowEffectToCurrentTheme(); } else { - ThemeManager.Instance.RemoveDropShadowEffectFromCurrentTheme(); + _theme.RemoveDropShadowEffectFromCurrentTheme(); } Settings.UseDropShadowEffect = value; + OnPropertyChanged(nameof(DropShadowEffect)); } } @@ -94,12 +117,25 @@ public partial class SettingsPaneThemeViewModel : BaseModel set => Settings.ResultSubItemFontSize = value; } - private List _themes; - public List Themes => _themes ??= ThemeManager.Instance.LoadAvailableThemes(); - public class ColorSchemeData : DropdownDataGeneric { } public List ColorSchemes { get; } = DropdownDataGeneric.GetValues("ColorScheme"); + public string ColorScheme + { + get => Settings.ColorScheme; + set + { + ThemeManagerForColorSchemeSwitch.Current.ApplicationTheme = value switch + { + Constant.Light => ApplicationTheme.Light, + Constant.Dark => ApplicationTheme.Dark, + Constant.System => null, + _ => ThemeManagerForColorSchemeSwitch.Current.ApplicationTheme + }; + Settings.ColorScheme = value; + _ = _theme.RefreshFrameAsync(); + } + } public List TimeFormatList { get; } = new() { @@ -174,6 +210,33 @@ public partial class SettingsPaneThemeViewModel : BaseModel public class AnimationSpeedData : DropdownDataGeneric { } public List AnimationSpeeds { get; } = DropdownDataGeneric.GetValues("AnimationSpeed"); + public class BackdropTypeData : DropdownDataGeneric { } + + public List BackdropTypesList { get; } = + DropdownDataGeneric.GetValues("BackdropTypes"); + + public BackdropTypes BackdropType + { + get => Enum.IsDefined(typeof(BackdropTypes), Settings.BackdropType) + ? Settings.BackdropType + : BackdropTypes.None; + set + { + if (!Enum.IsDefined(typeof(BackdropTypes), value)) + { + value = BackdropTypes.None; + } + + Settings.BackdropType = value; + + // Can only apply blur because drop shadow effect is not supported with backdrop + // So drop shadow effect has been disabled + _ = _theme.SetBlurForWindowAsync(); + + OnPropertyChanged(nameof(IsDropShadowEnabled)); + } + } + public bool UseSound { get => Settings.UseSound; @@ -219,37 +282,37 @@ public partial class SettingsPaneThemeViewModel : BaseModel { var results = new List { - new Result + new() { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleExplorer"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleExplorer"), + Title = App.API.GetTranslation("SampleTitleExplorer"), + SubTitle = App.API.GetTranslation("SampleSubTitleExplorer"), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png" ) }, - new Result + new() { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleWebSearch"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleWebSearch"), + Title = App.API.GetTranslation("SampleTitleWebSearch"), + SubTitle = App.API.GetTranslation("SampleSubTitleWebSearch"), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png" ) }, - new Result + new() { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProgram"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProgram"), + Title = App.API.GetTranslation("SampleTitleProgram"), + SubTitle = App.API.GetTranslation("SampleSubTitleProgram"), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png" ) }, - new Result + new() { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProcessKiller"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProcessKiller"), + Title = App.API.GetTranslation("SampleTitleProcessKiller"), + SubTitle = App.API.GetTranslation("SampleSubTitleProcessKiller"), IcoPath = Path.Combine( Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png" @@ -281,7 +344,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel set { Settings.QueryBoxFont = value.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); + _theme.UpdateFonts(); } } @@ -303,7 +366,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel Settings.QueryBoxFontStretch = value.Stretch.ToString(); Settings.QueryBoxFontWeight = value.Weight.ToString(); Settings.QueryBoxFontStyle = value.Style.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); + _theme.UpdateFonts(); } } @@ -325,7 +388,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel set { Settings.ResultFont = value.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); + _theme.UpdateFonts(); } } @@ -347,7 +410,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel Settings.ResultFontStretch = value.Stretch.ToString(); Settings.ResultFontWeight = value.Weight.ToString(); Settings.ResultFontStyle = value.Style.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); + _theme.UpdateFonts(); } } @@ -355,9 +418,9 @@ public partial class SettingsPaneThemeViewModel : BaseModel { get { - if (Fonts.SystemFontFamilies.Count(o => + if (Fonts.SystemFontFamilies.Any(o => o.FamilyNames.Values != null && - o.FamilyNames.Values.Contains(Settings.ResultSubFont)) > 0) + o.FamilyNames.Values.Contains(Settings.ResultSubFont))) { var font = new FontFamily(Settings.ResultSubFont); return font; @@ -371,7 +434,7 @@ public partial class SettingsPaneThemeViewModel : BaseModel set { Settings.ResultSubFont = value.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); + _theme.UpdateFonts(); } } @@ -392,34 +455,23 @@ public partial class SettingsPaneThemeViewModel : BaseModel Settings.ResultSubFontStretch = value.Stretch.ToString(); Settings.ResultSubFontWeight = value.Weight.ToString(); Settings.ResultSubFontStyle = value.Style.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); + _theme.UpdateFonts(); } } public string ThemeImage => Constant.QueryTextBoxIconImagePath; + public SettingsPaneThemeViewModel(Settings settings) + { + Settings = settings; + } + [RelayCommand] private void OpenThemesFolder() { App.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Themes)); } - public void UpdateColorScheme() - { - ThemeManagerForColorSchemeSwitch.Current.ApplicationTheme = Settings.ColorScheme switch - { - Constant.Light => ApplicationTheme.Light, - Constant.Dark => ApplicationTheme.Dark, - Constant.System => null, - _ => ThemeManagerForColorSchemeSwitch.Current.ApplicationTheme - }; - } - - public SettingsPaneThemeViewModel(Settings settings) - { - Settings = settings; - } - [RelayCommand] public void Reset() { diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml.cs index de6a4df5e..1ecc02aa6 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml.cs @@ -1,6 +1,8 @@ -using System; -using System.Windows.Navigation; +using System.Windows.Navigation; +using Flow.Launcher.Core; using Flow.Launcher.SettingPages.ViewModels; +using Flow.Launcher.Infrastructure.UserSettings; +using CommunityToolkit.Mvvm.DependencyInjection; namespace Flow.Launcher.SettingPages.Views; @@ -12,8 +14,8 @@ public partial class SettingsPaneAbout { if (!IsInitialized) { - if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings, Updater: { } updater }) - throw new ArgumentException("Settings are required for SettingsPaneAbout."); + var settings = Ioc.Default.GetRequiredService(); + var updater = Ioc.Default.GetRequiredService(); _viewModel = new SettingsPaneAboutViewModel(settings, updater); DataContext = _viewModel; InitializeComponent(); diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml.cs index f95015b2e..dd7fd13a9 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml.cs @@ -1,5 +1,7 @@ -using System; -using System.Windows.Navigation; +using System.Windows.Navigation; +using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Core; +using Flow.Launcher.Core.Configuration; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.SettingPages.ViewModels; @@ -13,8 +15,9 @@ public partial class SettingsPaneGeneral { if (!IsInitialized) { - if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings, Updater: {} updater, Portable: {} portable }) - throw new ArgumentException("Settings, Updater and Portable are required for SettingsPaneGeneral."); + var settings = Ioc.Default.GetRequiredService(); + var updater = Ioc.Default.GetRequiredService(); + var portable = Ioc.Default.GetRequiredService(); _viewModel = new SettingsPaneGeneralViewModel(settings, updater, portable); DataContext = _viewModel; InitializeComponent(); diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml index 22e3960ef..b1d72ede5 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml @@ -34,8 +34,7 @@ @@ -46,8 +45,7 @@ Sub="{DynamicResource previewHotkeyToolTip}"> @@ -105,8 +103,7 @@ Type="Inside"> @@ -221,8 +213,7 @@ @@ -244,8 +234,7 @@ @@ -267,8 +255,7 @@ diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs index 061eabf51..eb100da0c 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneHotkey.xaml.cs @@ -1,6 +1,7 @@ -using System; -using System.Windows.Navigation; +using System.Windows.Navigation; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.SettingPages.ViewModels; +using Flow.Launcher.Infrastructure.UserSettings; namespace Flow.Launcher.SettingPages.Views; @@ -12,8 +13,7 @@ public partial class SettingsPaneHotkey { if (!IsInitialized) { - if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings }) - throw new ArgumentException("Settings are required for SettingsPaneHotkey."); + var settings = Ioc.Default.GetRequiredService(); _viewModel = new SettingsPaneHotkeyViewModel(settings); DataContext = _viewModel; InitializeComponent(); diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs index db4763319..3bd24bc13 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs @@ -1,10 +1,11 @@ -using System; -using System.ComponentModel; +using System.ComponentModel; using System.Windows.Data; using System.Windows.Input; using System.Windows.Navigation; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.SettingPages.ViewModels; using Flow.Launcher.ViewModel; +using Flow.Launcher.Infrastructure.UserSettings; namespace Flow.Launcher.SettingPages.Views; @@ -16,8 +17,7 @@ public partial class SettingsPanePluginStore { if (!IsInitialized) { - if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings }) - throw new ArgumentException($"Settings are required for {nameof(SettingsPanePluginStore)}."); + var settings = Ioc.Default.GetRequiredService(); _viewModel = new SettingsPanePluginStoreViewModel(); DataContext = _viewModel; InitializeComponent(); diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs index d48505c3d..f6b435186 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs @@ -1,7 +1,8 @@ -using System; -using System.Windows.Input; +using System.Windows.Input; using System.Windows.Navigation; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.SettingPages.ViewModels; +using Flow.Launcher.Infrastructure.UserSettings; namespace Flow.Launcher.SettingPages.Views; @@ -13,8 +14,7 @@ public partial class SettingsPanePlugins { if (!IsInitialized) { - if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings }) - throw new ArgumentException("Settings are required for SettingsPaneHotkey."); + var settings = Ioc.Default.GetRequiredService(); _viewModel = new SettingsPanePluginsViewModel(settings); DataContext = _viewModel; InitializeComponent(); diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml.cs index 95c88d627..26350b8bb 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml.cs @@ -1,6 +1,8 @@ -using System; -using System.Windows.Navigation; +using System.Windows.Navigation; +using CommunityToolkit.Mvvm.DependencyInjection; +using Flow.Launcher.Core; using Flow.Launcher.SettingPages.ViewModels; +using Flow.Launcher.Infrastructure.UserSettings; namespace Flow.Launcher.SettingPages.Views; @@ -12,8 +14,8 @@ public partial class SettingsPaneProxy { if (!IsInitialized) { - if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings, Updater: { } updater }) - throw new ArgumentException($"Settings are required for {nameof(SettingsPaneProxy)}."); + var settings = Ioc.Default.GetRequiredService(); + var updater = Ioc.Default.GetRequiredService(); _viewModel = new SettingsPaneProxyViewModel(settings, updater); DataContext = _viewModel; InitializeComponent(); diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml index 18835259b..614237146 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml @@ -303,7 +303,7 @@ Width="400" Margin="40 30 0 30" SnapsToDevicePixels="True" - Style="{DynamicResource WindowBorderStyle}"> + Style="{DynamicResource PreviewWindowBorderStyle}"> @@ -370,17 +370,6 @@ - - - - + + + + + + + + + + + + + + + + + SelectedValue="{Binding ColorScheme, Mode=TwoWay}" + SelectedValuePath="Value" /> diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml.cs index 7f32728c2..22de4fcc0 100644 --- a/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml.cs +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneTheme.xaml.cs @@ -1,10 +1,8 @@ -using System; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; using System.Windows.Navigation; +using CommunityToolkit.Mvvm.DependencyInjection; using Flow.Launcher.SettingPages.ViewModels; using Page = ModernWpf.Controls.Page; +using Flow.Launcher.Infrastructure.UserSettings; namespace Flow.Launcher.SettingPages.Views; @@ -16,8 +14,7 @@ public partial class SettingsPaneTheme : Page { if (!IsInitialized) { - if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings }) - throw new ArgumentException($"Settings are required for {nameof(SettingsPaneTheme)}."); + var settings = Ioc.Default.GetRequiredService(); _viewModel = new SettingsPaneThemeViewModel(settings); DataContext = _viewModel; InitializeComponent(); @@ -25,9 +22,4 @@ public partial class SettingsPaneTheme : Page base.OnNavigatedTo(e); } - - private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e) - { - _viewModel.UpdateColorScheme(); - } } diff --git a/Flow.Launcher/SettingWindow.xaml.cs b/Flow.Launcher/SettingWindow.xaml.cs index f87194c30..28140f024 100644 --- a/Flow.Launcher/SettingWindow.xaml.cs +++ b/Flow.Launcher/SettingWindow.xaml.cs @@ -4,9 +4,7 @@ using System.Windows.Forms; using System.Windows.Input; using System.Windows.Interop; using CommunityToolkit.Mvvm.DependencyInjection; -using Flow.Launcher.Core; -using Flow.Launcher.Core.Configuration; -using Flow.Launcher.Helper; +using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using Flow.Launcher.SettingPages.Views; @@ -18,8 +16,6 @@ namespace Flow.Launcher; public partial class SettingWindow { - private readonly Updater _updater; - private readonly IPortable _portable; private readonly IPublicAPI _api; private readonly Settings _settings; private readonly SettingWindowViewModel _viewModel; @@ -30,8 +26,6 @@ public partial class SettingWindow _settings = Ioc.Default.GetRequiredService(); DataContext = viewModel; _viewModel = viewModel; - _updater = Ioc.Default.GetRequiredService(); - _portable = Ioc.Default.GetRequiredService(); _api = Ioc.Default.GetRequiredService(); InitializePosition(); InitializeComponent(); @@ -98,13 +92,13 @@ public partial class SettingWindow { if (WindowState == WindowState.Maximized) { - MaximizeButton.Visibility = Visibility.Collapsed; + MaximizeButton.Visibility = Visibility.Hidden; RestoreButton.Visibility = Visibility.Visible; } else { MaximizeButton.Visibility = Visibility.Visible; - RestoreButton.Visibility = Visibility.Collapsed; + RestoreButton.Visibility = Visibility.Hidden; } } @@ -149,8 +143,8 @@ public partial class SettingWindow private double WindowLeft() { var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); + var dip1 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); + var dip2 = Win32Helper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); var left = (dip2.X - ActualWidth) / 2 + dip1.X; return left; } @@ -158,18 +152,17 @@ public partial class SettingWindow private double WindowTop() { var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); + var dip1 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); + var dip2 = Win32Helper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); var top = (dip2.Y - ActualHeight) / 2 + dip1.Y - 20; return top; } private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) { - var paneData = new PaneData(_settings, _updater, _portable); if (args.IsSettingsSelected) { - ContentFrame.Navigate(typeof(SettingsPaneGeneral), paneData); + ContentFrame.Navigate(typeof(SettingsPaneGeneral)); } else { @@ -191,7 +184,7 @@ public partial class SettingWindow nameof(About) => typeof(SettingsPaneAbout), _ => typeof(SettingsPaneGeneral) }; - ContentFrame.Navigate(pageType, paneData); + ContentFrame.Navigate(pageType); } } @@ -211,6 +204,4 @@ public partial class SettingWindow { NavView.SelectedItem ??= NavView.MenuItems[0]; /* Set First Page */ } - - public record PaneData(Settings Settings, Updater Updater, IPortable Portable); } diff --git a/Flow.Launcher/Themes/Base.xaml b/Flow.Launcher/Themes/Base.xaml index b96cb661e..3756e7d27 100644 --- a/Flow.Launcher/Themes/Base.xaml +++ b/Flow.Launcher/Themes/Base.xaml @@ -27,7 +27,6 @@ @@ -204,12 +180,10 @@ + + - @@ -135,8 +138,8 @@ BasedOn="{StaticResource BaseSearchIconStyle}" TargetType="{x:Type Path}"> - - + + + @@ -33,7 +43,9 @@ + Opacity="0.6" + Color="#33000000" /> @@ -112,7 +124,7 @@ - - + + @@ -75,7 +79,7 @@ x:Key="SeparatorStyle" BasedOn="{StaticResource BaseSeparatorStyle}" TargetType="{x:Type Rectangle}"> - + diff --git a/Flow.Launcher/Themes/Darker.xaml b/Flow.Launcher/Themes/Darker.xaml index d1abbe978..7d2614be8 100644 --- a/Flow.Launcher/Themes/Darker.xaml +++ b/Flow.Launcher/Themes/Darker.xaml @@ -29,7 +29,9 @@ - #0e172c + #cc1081 \ No newline at end of file diff --git a/Flow.Launcher/Themes/Ubuntu.xaml b/Flow.Launcher/Themes/Ubuntu.xaml index 3c9e0a125..4007370f9 100644 --- a/Flow.Launcher/Themes/Ubuntu.xaml +++ b/Flow.Launcher/Themes/Ubuntu.xaml @@ -32,7 +32,7 @@ x:Key="QueryBoxStyle" BasedOn="{StaticResource BaseQueryBoxStyle}" TargetType="{x:Type TextBox}"> - + @@ -43,7 +43,7 @@ x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}"> - + @@ -56,15 +56,7 @@ TargetType="{x:Type Border}"> - - - - - - - - - + @@ -96,7 +88,7 @@ TargetType="{x:Type Rectangle}"> - + @@ -174,7 +166,7 @@ x:Key="ClockPanel" BasedOn="{StaticResource ClockPanel}" TargetType="{x:Type StackPanel}"> - + - - - + + + + + + + + + + + + + + + + + + + + +