mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' of github.com:Flow-Launcher/Flow.Launcher into net7.0
This commit is contained in:
commit
704c26ca3d
61 changed files with 1072 additions and 531 deletions
26
.github/actions/spelling/expect.txt
vendored
26
.github/actions/spelling/expect.txt
vendored
|
|
@ -62,3 +62,29 @@ TobiasSekan
|
|||
Img
|
||||
img
|
||||
resx
|
||||
bak
|
||||
tmp
|
||||
directx
|
||||
mvvm
|
||||
dlg
|
||||
ddd
|
||||
dddd
|
||||
clearlogfolder
|
||||
ACCENT_ENABLE_TRANSPARENTGRADIENT
|
||||
ACCENT_ENABLE_BLURBEHIND
|
||||
WCA_ACCENT_POLICY
|
||||
HGlobal
|
||||
dopusrt
|
||||
firefox
|
||||
msedge
|
||||
svgc
|
||||
ime
|
||||
zindex
|
||||
txb
|
||||
btn
|
||||
otf
|
||||
searchplugin
|
||||
Noresult
|
||||
wpftk
|
||||
mkv
|
||||
flac
|
||||
|
|
|
|||
|
|
@ -259,9 +259,16 @@ namespace Flow.Launcher.Core.Plugin
|
|||
|
||||
await using var registeredEvent = token.Register(() =>
|
||||
{
|
||||
if (!process.HasExited)
|
||||
process.Kill();
|
||||
sourceBuffer.Dispose();
|
||||
try
|
||||
{
|
||||
if (!process.HasExited)
|
||||
process.Kill();
|
||||
sourceBuffer.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception("|JsonRPCPlugin.ExecuteAsync|Exception when kill process", e);
|
||||
}
|
||||
});
|
||||
|
||||
try
|
||||
|
|
@ -288,7 +295,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
}
|
||||
|
||||
sourceBuffer.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
|
||||
return sourceBuffer;
|
||||
}
|
||||
|
||||
|
|
@ -376,7 +383,8 @@ namespace Flow.Launcher.Core.Plugin
|
|||
sep.SetResourceReference(Separator.BackgroundProperty, "Color03B"); /* for theme change */
|
||||
var panel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Vertical, VerticalAlignment = VerticalAlignment.Center,
|
||||
Orientation = Orientation.Vertical,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = settingLabelPanelMargin
|
||||
};
|
||||
RowDefinition gridRow = new RowDefinition();
|
||||
|
|
@ -390,8 +398,10 @@ namespace Flow.Launcher.Core.Plugin
|
|||
};
|
||||
var desc = new TextBlock()
|
||||
{
|
||||
Text = attribute.Description, FontSize = 12,
|
||||
VerticalAlignment = VerticalAlignment.Center,Margin = settingDescMargin,
|
||||
Text = attribute.Description,
|
||||
FontSize = 12,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = settingDescMargin,
|
||||
TextWrapping = TextWrapping.WrapWithOverflow
|
||||
};
|
||||
desc.SetResourceReference(TextBlock.ForegroundProperty, "Color04B");
|
||||
|
|
@ -405,7 +415,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
panel.Children.Add(name);
|
||||
panel.Children.Add(desc);
|
||||
}
|
||||
|
||||
|
||||
|
||||
Grid.SetColumn(panel, 0);
|
||||
Grid.SetRow(panel, rowCount);
|
||||
|
|
@ -420,20 +430,20 @@ namespace Flow.Launcher.Core.Plugin
|
|||
{
|
||||
Text = attribute.Description.Replace("\\r\\n", "\r\n"),
|
||||
Margin = settingTextBlockMargin,
|
||||
Padding = new Thickness(0,0,0,0),
|
||||
Padding = new Thickness(0, 0, 0, 0),
|
||||
HorizontalAlignment = System.Windows.HorizontalAlignment.Left,
|
||||
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;
|
||||
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;
|
||||
}
|
||||
case "input":
|
||||
{
|
||||
|
|
@ -449,50 +459,49 @@ namespace Flow.Launcher.Core.Plugin
|
|||
Settings[attribute.Name] = textBox.Text;
|
||||
};
|
||||
contentControl = textBox;
|
||||
Grid.SetColumn(contentControl, 1);
|
||||
Grid.SetRow(contentControl, rowCount);
|
||||
if (rowCount != 0)
|
||||
mainPanel.Children.Add(sep);
|
||||
Grid.SetRow(sep, rowCount);
|
||||
Grid.SetColumn(sep, 0);
|
||||
Grid.SetColumnSpan(sep, 2);
|
||||
break;
|
||||
Grid.SetColumn(contentControl, 1);
|
||||
Grid.SetRow(contentControl, rowCount);
|
||||
if (rowCount != 0)
|
||||
mainPanel.Children.Add(sep);
|
||||
Grid.SetRow(sep, rowCount);
|
||||
Grid.SetColumn(sep, 0);
|
||||
Grid.SetColumnSpan(sep, 2);
|
||||
break;
|
||||
}
|
||||
case "inputWithFileBtn":
|
||||
{
|
||||
var textBox = new TextBox()
|
||||
{
|
||||
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"
|
||||
};
|
||||
var dockPanel = new DockPanel()
|
||||
{
|
||||
Margin = settingControlMargin
|
||||
};
|
||||
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);
|
||||
Grid.SetRow(sep, rowCount);
|
||||
Grid.SetColumn(sep, 0);
|
||||
Grid.SetColumnSpan(sep, 2);
|
||||
break;
|
||||
}
|
||||
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"
|
||||
};
|
||||
var dockPanel = new DockPanel()
|
||||
{
|
||||
Margin = settingControlMargin
|
||||
};
|
||||
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);
|
||||
Grid.SetRow(sep, rowCount);
|
||||
Grid.SetColumn(sep, 0);
|
||||
Grid.SetColumnSpan(sep, 2);
|
||||
break;
|
||||
}
|
||||
case "textarea":
|
||||
{
|
||||
var textBox = new TextBox()
|
||||
|
|
@ -511,14 +520,14 @@ namespace Flow.Launcher.Core.Plugin
|
|||
Settings[attribute.Name] = ((TextBox)sender).Text;
|
||||
};
|
||||
contentControl = textBox;
|
||||
Grid.SetColumn(contentControl, 1);
|
||||
Grid.SetRow(contentControl, rowCount);
|
||||
if (rowCount != 0)
|
||||
mainPanel.Children.Add(sep);
|
||||
Grid.SetRow(sep, rowCount);
|
||||
Grid.SetColumn(sep, 0);
|
||||
Grid.SetColumnSpan(sep, 2);
|
||||
break;
|
||||
Grid.SetColumn(contentControl, 1);
|
||||
Grid.SetRow(contentControl, rowCount);
|
||||
if (rowCount != 0)
|
||||
mainPanel.Children.Add(sep);
|
||||
Grid.SetRow(sep, rowCount);
|
||||
Grid.SetColumn(sep, 0);
|
||||
Grid.SetColumnSpan(sep, 2);
|
||||
break;
|
||||
}
|
||||
case "passwordBox":
|
||||
{
|
||||
|
|
@ -535,14 +544,14 @@ namespace Flow.Launcher.Core.Plugin
|
|||
Settings[attribute.Name] = ((PasswordBox)sender).Password;
|
||||
};
|
||||
contentControl = passwordBox;
|
||||
Grid.SetColumn(contentControl, 1);
|
||||
Grid.SetRow(contentControl, rowCount);
|
||||
if (rowCount != 0)
|
||||
mainPanel.Children.Add(sep);
|
||||
Grid.SetRow(sep, rowCount);
|
||||
Grid.SetColumn(sep, 0);
|
||||
Grid.SetColumnSpan(sep, 2);
|
||||
break;
|
||||
Grid.SetColumn(contentControl, 1);
|
||||
Grid.SetRow(contentControl, rowCount);
|
||||
if (rowCount != 0)
|
||||
mainPanel.Children.Add(sep);
|
||||
Grid.SetRow(sep, rowCount);
|
||||
Grid.SetColumn(sep, 0);
|
||||
Grid.SetColumnSpan(sep, 2);
|
||||
break;
|
||||
}
|
||||
case "dropdown":
|
||||
{
|
||||
|
|
@ -559,14 +568,14 @@ namespace Flow.Launcher.Core.Plugin
|
|||
Settings[attribute.Name] = (string)((System.Windows.Controls.ComboBox)sender).SelectedItem;
|
||||
};
|
||||
contentControl = comboBox;
|
||||
Grid.SetColumn(contentControl, 1);
|
||||
Grid.SetRow(contentControl, rowCount);
|
||||
if (rowCount != 0)
|
||||
mainPanel.Children.Add(sep);
|
||||
Grid.SetRow(sep, rowCount);
|
||||
Grid.SetColumn(sep, 0);
|
||||
Grid.SetColumnSpan(sep, 2);
|
||||
break;
|
||||
Grid.SetColumn(contentControl, 1);
|
||||
Grid.SetRow(contentControl, rowCount);
|
||||
if (rowCount != 0)
|
||||
mainPanel.Children.Add(sep);
|
||||
Grid.SetRow(sep, rowCount);
|
||||
Grid.SetColumn(sep, 0);
|
||||
Grid.SetColumnSpan(sep, 2);
|
||||
break;
|
||||
}
|
||||
case "checkbox":
|
||||
var checkBox = new CheckBox
|
||||
|
|
@ -592,13 +601,11 @@ namespace Flow.Launcher.Core.Plugin
|
|||
case "hyperlink":
|
||||
var hyperlink = new Hyperlink
|
||||
{
|
||||
ToolTip = attribute.Description,
|
||||
NavigateUri = attribute.url
|
||||
ToolTip = attribute.Description, NavigateUri = attribute.url
|
||||
};
|
||||
var linkbtn = new System.Windows.Controls.Button
|
||||
{
|
||||
HorizontalAlignment = System.Windows.HorizontalAlignment.Right,
|
||||
Margin = settingControlMargin
|
||||
HorizontalAlignment = System.Windows.HorizontalAlignment.Right, Margin = settingControlMargin
|
||||
};
|
||||
linkbtn.Content = attribute.urlLabel;
|
||||
|
||||
|
|
@ -619,7 +626,7 @@ namespace Flow.Launcher.Core.Plugin
|
|||
mainPanel.Children.Add(panel);
|
||||
mainPanel.Children.Add(contentControl);
|
||||
rowCount++;
|
||||
|
||||
|
||||
}
|
||||
return settingWindow;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
{
|
||||
_themeDirectories.Add(DirectoryPath);
|
||||
_themeDirectories.Add(UserDirectoryPath);
|
||||
MakesureThemeDirectoriesExist();
|
||||
MakeSureThemeDirectoriesExist();
|
||||
|
||||
var dicts = Application.Current.Resources.MergedDictionaries;
|
||||
_oldResource = dicts.First(d =>
|
||||
|
|
@ -55,20 +55,17 @@ namespace Flow.Launcher.Core.Resource
|
|||
_oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath);
|
||||
}
|
||||
|
||||
private void MakesureThemeDirectoriesExist()
|
||||
private void MakeSureThemeDirectoriesExist()
|
||||
{
|
||||
foreach (string dir in _themeDirectories)
|
||||
foreach (var dir in _themeDirectories.Where(dir => !Directory.Exists(dir)))
|
||||
{
|
||||
if (!Directory.Exists(dir))
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Theme.MakesureThemeDirectoriesExist|Exception when create directory <{dir}>", e);
|
||||
}
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Theme.MakesureThemeDirectoriesExist|Exception when create directory <{dir}>", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -82,13 +79,14 @@ namespace Flow.Launcher.Core.Resource
|
|||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
throw new DirectoryNotFoundException("Theme path can't be found <{path}>");
|
||||
|
||||
Settings.Theme = theme;
|
||||
|
||||
|
||||
// reload all resources even if the theme itself hasn't changed in order to pickup changes
|
||||
// to things like fonts
|
||||
UpdateResourceDictionary(GetResourceDictionary());
|
||||
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)
|
||||
{
|
||||
|
|
@ -134,9 +132,9 @@ namespace Flow.Launcher.Core.Resource
|
|||
_oldResource = dictionaryToUpdate;
|
||||
}
|
||||
|
||||
private ResourceDictionary CurrentThemeResourceDictionary()
|
||||
private ResourceDictionary GetThemeResourceDictionary(string theme)
|
||||
{
|
||||
var uri = GetThemePath(Settings.Theme);
|
||||
var uri = GetThemePath(theme);
|
||||
var dict = new ResourceDictionary
|
||||
{
|
||||
Source = new Uri(uri, UriKind.Absolute)
|
||||
|
|
@ -145,10 +143,12 @@ namespace Flow.Launcher.Core.Resource
|
|||
return dict;
|
||||
}
|
||||
|
||||
public ResourceDictionary GetResourceDictionary()
|
||||
private ResourceDictionary CurrentThemeResourceDictionary() => GetThemeResourceDictionary(Settings.Theme);
|
||||
|
||||
public ResourceDictionary GetResourceDictionary(string theme)
|
||||
{
|
||||
var dict = CurrentThemeResourceDictionary();
|
||||
|
||||
var dict = GetThemeResourceDictionary(theme);
|
||||
|
||||
if (dict["QueryBoxStyle"] is Style queryBoxStyle &&
|
||||
dict["QuerySuggestionBoxStyle"] is Style querySuggestionBoxStyle)
|
||||
{
|
||||
|
|
@ -200,6 +200,11 @@ namespace Flow.Launcher.Core.Resource
|
|||
return dict;
|
||||
}
|
||||
|
||||
private ResourceDictionary GetCurrentResourceDictionary( )
|
||||
{
|
||||
return GetResourceDictionary(Settings.Theme);
|
||||
}
|
||||
|
||||
public List<string> LoadAvailableThemes()
|
||||
{
|
||||
List<string> themes = new List<string>();
|
||||
|
|
@ -229,7 +234,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
|
||||
public void AddDropShadowEffectToCurrentTheme()
|
||||
{
|
||||
var dict = GetResourceDictionary();
|
||||
var dict = GetCurrentResourceDictionary();
|
||||
|
||||
var windowBorderStyle = dict["WindowBorderStyle"] as Style;
|
||||
|
||||
|
|
@ -273,7 +278,7 @@ namespace Flow.Launcher.Core.Resource
|
|||
|
||||
public void RemoveDropShadowEffectFromCurrentTheme()
|
||||
{
|
||||
var dict = CurrentThemeResourceDictionary();
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ namespace Flow.Launcher.Core
|
|||
|
||||
public async Task UpdateAppAsync(IPublicAPI api, bool silentUpdate = true)
|
||||
{
|
||||
await UpdateLock.WaitAsync();
|
||||
await UpdateLock.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (!silentUpdate)
|
||||
|
|
@ -88,9 +88,13 @@ namespace Flow.Launcher.Core
|
|||
UpdateManager.RestartApp(Constant.ApplicationFileName);
|
||||
}
|
||||
}
|
||||
catch (Exception e) when (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
|
||||
if ((e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException))
|
||||
Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e);
|
||||
else
|
||||
Log.Exception($"|Updater.UpdateApp|Error Occurred", e);
|
||||
|
||||
if (!silentUpdate)
|
||||
api.ShowMsg(api.GetTranslation("update_flowlauncher_fail"),
|
||||
api.GetTranslation("update_flowlauncher_check_connection"));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
using System;
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
|
|
@ -16,7 +18,7 @@ namespace Flow.Launcher.Infrastructure
|
|||
/// <summary>
|
||||
/// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy
|
||||
/// </summary>
|
||||
public static T NonNull<T>(this T obj)
|
||||
public static T NonNull<T>(this T? obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Navigation;
|
||||
|
||||
namespace Flow.Launcher.Infrastructure.Hotkey
|
||||
{
|
||||
|
|
@ -11,10 +12,10 @@ namespace Flow.Launcher.Infrastructure.Hotkey
|
|||
public bool Shift { get; set; }
|
||||
public bool Win { get; set; }
|
||||
public bool Ctrl { get; set; }
|
||||
public Key CharKey { get; set; }
|
||||
|
||||
public Key CharKey { get; set; } = Key.None;
|
||||
|
||||
Dictionary<Key, string> specialSymbolDictionary = new Dictionary<Key, string>
|
||||
private static readonly Dictionary<Key, string> specialSymbolDictionary = new Dictionary<Key, string>
|
||||
{
|
||||
{Key.Space, "Space"},
|
||||
{Key.Oem3, "~"}
|
||||
|
|
@ -27,19 +28,19 @@ namespace Flow.Launcher.Infrastructure.Hotkey
|
|||
ModifierKeys modifierKeys = ModifierKeys.None;
|
||||
if (Alt)
|
||||
{
|
||||
modifierKeys = ModifierKeys.Alt;
|
||||
modifierKeys |= ModifierKeys.Alt;
|
||||
}
|
||||
if (Shift)
|
||||
{
|
||||
modifierKeys = modifierKeys | ModifierKeys.Shift;
|
||||
modifierKeys |= ModifierKeys.Shift;
|
||||
}
|
||||
if (Win)
|
||||
{
|
||||
modifierKeys = modifierKeys | ModifierKeys.Windows;
|
||||
modifierKeys |= ModifierKeys.Windows;
|
||||
}
|
||||
if (Ctrl)
|
||||
{
|
||||
modifierKeys = modifierKeys | ModifierKeys.Control;
|
||||
modifierKeys |= ModifierKeys.Control;
|
||||
}
|
||||
return modifierKeys;
|
||||
}
|
||||
|
|
@ -86,7 +87,7 @@ namespace Flow.Launcher.Infrastructure.Hotkey
|
|||
Ctrl = true;
|
||||
keys.Remove("Ctrl");
|
||||
}
|
||||
if (keys.Count > 0)
|
||||
if (keys.Count == 1)
|
||||
{
|
||||
string charKey = keys[0];
|
||||
KeyValuePair<Key, string>? specialSymbolPair = specialSymbolDictionary.FirstOrDefault(pair => pair.Value == charKey);
|
||||
|
|
@ -110,36 +111,75 @@ namespace Flow.Launcher.Infrastructure.Hotkey
|
|||
|
||||
public override string ToString()
|
||||
{
|
||||
string text = string.Empty;
|
||||
List<string> keys = new List<string>();
|
||||
if (Ctrl)
|
||||
{
|
||||
text += "Ctrl + ";
|
||||
keys.Add("Ctrl");
|
||||
}
|
||||
if (Alt)
|
||||
{
|
||||
text += "Alt + ";
|
||||
keys.Add("Alt");
|
||||
}
|
||||
if (Shift)
|
||||
{
|
||||
text += "Shift + ";
|
||||
keys.Add("Shift");
|
||||
}
|
||||
if (Win)
|
||||
{
|
||||
text += "Win + ";
|
||||
keys.Add("Win");
|
||||
}
|
||||
|
||||
if (CharKey != Key.None)
|
||||
{
|
||||
text += specialSymbolDictionary.ContainsKey(CharKey)
|
||||
keys.Add(specialSymbolDictionary.ContainsKey(CharKey)
|
||||
? specialSymbolDictionary[CharKey]
|
||||
: CharKey.ToString();
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
text = text.Remove(text.Length - 3);
|
||||
: CharKey.ToString());
|
||||
}
|
||||
return string.Join(" + ", keys);
|
||||
}
|
||||
|
||||
return text;
|
||||
public bool Validate()
|
||||
{
|
||||
switch (CharKey)
|
||||
{
|
||||
case Key.LeftAlt:
|
||||
case Key.RightAlt:
|
||||
case Key.LeftCtrl:
|
||||
case Key.RightCtrl:
|
||||
case Key.LeftShift:
|
||||
case Key.RightShift:
|
||||
case Key.LWin:
|
||||
case Key.RWin:
|
||||
return false;
|
||||
default:
|
||||
if (ModifierKeys == ModifierKeys.None)
|
||||
{
|
||||
return !((CharKey >= Key.A && CharKey <= Key.Z) ||
|
||||
(CharKey >= Key.D0 && CharKey <= Key.D9) ||
|
||||
(CharKey >= Key.NumPad0 && CharKey <= Key.NumPad9));
|
||||
}
|
||||
else
|
||||
{
|
||||
return CharKey != Key.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is HotkeyModel other)
|
||||
{
|
||||
return ModifierKeys == other.ModifierKeys && CharKey == other.CharKey;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(ModifierKeys, CharKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
|
|
@ -11,62 +12,82 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
/// </summary>
|
||||
public class JsonStorage<T> where T : new()
|
||||
{
|
||||
protected T _data;
|
||||
protected T? Data;
|
||||
|
||||
// need a new directory name
|
||||
public const string DirectoryName = "Settings";
|
||||
public const string FileSuffix = ".json";
|
||||
public string FilePath { get; set; }
|
||||
public string DirectoryPath { get; set; }
|
||||
|
||||
protected string FilePath { get; init; } = null!;
|
||||
|
||||
private string TempFilePath => $"{FilePath}.tmp";
|
||||
|
||||
private string BackupFilePath => $"{FilePath}.bak";
|
||||
|
||||
protected string DirectoryPath { get; init; } = null!;
|
||||
|
||||
|
||||
public T Load()
|
||||
{
|
||||
string? serialized = null;
|
||||
|
||||
if (File.Exists(FilePath))
|
||||
{
|
||||
var serialized = File.ReadAllText(FilePath);
|
||||
if (!string.IsNullOrWhiteSpace(serialized))
|
||||
serialized = File.ReadAllText(FilePath);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(serialized))
|
||||
{
|
||||
try
|
||||
{
|
||||
Deserialize(serialized);
|
||||
Data = JsonSerializer.Deserialize<T>(serialized) ?? TryLoadBackup() ?? LoadDefault();
|
||||
}
|
||||
else
|
||||
catch (JsonException)
|
||||
{
|
||||
LoadDefault();
|
||||
Data = TryLoadBackup() ?? LoadDefault();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LoadDefault();
|
||||
Data = TryLoadBackup() ?? LoadDefault();
|
||||
}
|
||||
return _data.NonNull();
|
||||
|
||||
return Data.NonNull();
|
||||
}
|
||||
|
||||
private void Deserialize(string serialized)
|
||||
{
|
||||
try
|
||||
{
|
||||
_data = JsonSerializer.Deserialize<T>(serialized);
|
||||
}
|
||||
catch (JsonException e)
|
||||
{
|
||||
LoadDefault();
|
||||
Log.Exception($"|JsonStorage.Deserialize|Deserialize error for json <{FilePath}>", e);
|
||||
}
|
||||
|
||||
if (_data == null)
|
||||
{
|
||||
LoadDefault();
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDefault()
|
||||
private T LoadDefault()
|
||||
{
|
||||
if (File.Exists(FilePath))
|
||||
{
|
||||
BackupOriginFile();
|
||||
}
|
||||
|
||||
_data = new T();
|
||||
Save();
|
||||
return new T();
|
||||
}
|
||||
|
||||
private T? TryLoadBackup()
|
||||
{
|
||||
if (!File.Exists(BackupFilePath))
|
||||
return default;
|
||||
|
||||
try
|
||||
{
|
||||
var data = JsonSerializer.Deserialize<T>(File.ReadAllText(BackupFilePath));
|
||||
|
||||
if (data != null)
|
||||
{
|
||||
Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully");
|
||||
File.Replace(BackupFilePath, FilePath, null);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
private void BackupOriginFile()
|
||||
|
|
@ -82,13 +103,22 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
|
||||
public void Save()
|
||||
{
|
||||
string serialized = JsonSerializer.Serialize(_data, new JsonSerializerOptions() { WriteIndented = true });
|
||||
string serialized = JsonSerializer.Serialize(Data,
|
||||
new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
});
|
||||
|
||||
File.WriteAllText(FilePath, serialized);
|
||||
File.WriteAllText(TempFilePath, serialized);
|
||||
|
||||
if (!File.Exists(FilePath))
|
||||
{
|
||||
File.Move(TempFilePath, FilePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
File.Replace(TempFilePath, FilePath, BackupFilePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Deprecated as of Flow Launcher v1.8.0, on 2021.06.21. " +
|
||||
"This is used only for Everything plugin v1.4.9 or below backwards compatibility")]
|
||||
public class JsonStrorage<T> : JsonStorage<T> where T : new() { }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ namespace Flow.Launcher.Infrastructure.Storage
|
|||
|
||||
public PluginJsonStorage(T data) : this()
|
||||
{
|
||||
_data = data;
|
||||
Data = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
public class Settings : BaseModel
|
||||
{
|
||||
private string language = "en";
|
||||
private string _theme = Constant.DefaultTheme;
|
||||
public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}";
|
||||
public string OpenResultModifiers { get; set; } = KeyConstant.Alt;
|
||||
public string ColorScheme { get; set; } = "System";
|
||||
|
|
@ -29,7 +30,18 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
public string Theme { get; set; } = Constant.DefaultTheme;
|
||||
public string Theme
|
||||
{
|
||||
get => _theme;
|
||||
set
|
||||
{
|
||||
if (value == _theme)
|
||||
return;
|
||||
_theme = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(MaxResultsToShow));
|
||||
}
|
||||
}
|
||||
public bool UseDropShadowEffect { get; set; } = false;
|
||||
public string QueryBoxFont { get; set; } = FontFamily.GenericSansSerif.Name;
|
||||
public string QueryBoxFontStyle { get; set; }
|
||||
|
|
@ -214,7 +226,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
}
|
||||
}
|
||||
public bool LeaveCmdOpen { get; set; }
|
||||
public bool HideWhenDeactive { get; set; } = true;
|
||||
public bool HideWhenDeactivated { get; set; } = true;
|
||||
public SearchWindowPositions SearchWindowPosition { get; set; } = SearchWindowPositions.MouseScreenCenter;
|
||||
public bool IgnoreHotkeysOnFullscreen { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>3.0.1</Version>
|
||||
<PackageVersion>3.0.1</PackageVersion>
|
||||
<AssemblyVersion>3.0.1</AssemblyVersion>
|
||||
<FileVersion>3.0.1</FileVersion>
|
||||
<Version>3.1.0</Version>
|
||||
<PackageVersion>3.1.0</PackageVersion>
|
||||
<AssemblyVersion>3.1.0</AssemblyVersion>
|
||||
<FileVersion>3.1.0</FileVersion>
|
||||
<PackageId>Flow.Launcher.Plugin</PackageId>
|
||||
<Authors>Flow-Launcher</Authors>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
|
|
@ -65,7 +65,7 @@
|
|||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.4.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
#pragma warning disable IDE0005
|
||||
using System.Windows;
|
||||
#pragma warning restore IDE0005
|
||||
|
||||
namespace Flow.Launcher.Plugin.SharedCommands
|
||||
{
|
||||
|
|
@ -206,22 +208,16 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
///</summary>
|
||||
public static string GetPreviousExistingDirectory(Func<string, bool> locationExists, string path)
|
||||
{
|
||||
var previousDirectoryPath = "";
|
||||
var index = path.LastIndexOf('\\');
|
||||
if (index > 0 && index < (path.Length - 1))
|
||||
{
|
||||
previousDirectoryPath = path.Substring(0, index + 1);
|
||||
if (!locationExists(previousDirectoryPath))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
string previousDirectoryPath = path.Substring(0, index + 1);
|
||||
return locationExists(previousDirectoryPath) ? previousDirectoryPath : "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return previousDirectoryPath;
|
||||
}
|
||||
|
||||
///<summary>
|
||||
|
|
@ -241,5 +237,33 @@ namespace Flow.Launcher.Plugin.SharedCommands
|
|||
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if <paramref name="parentPath"/> contains <paramref name="subPath"/>.
|
||||
/// From https://stackoverflow.com/a/66877016
|
||||
/// </summary>
|
||||
/// <param name="parentPath">Parent path</param>
|
||||
/// <param name="subPath">Sub path</param>
|
||||
/// <param name="allowEqual">If <see langword="true"/>, when <paramref name="parentPath"/> and <paramref name="subPath"/> are equal, returns <see langword="true"/></param>
|
||||
/// <returns></returns>
|
||||
public static bool PathContains(string parentPath, string subPath, bool allowEqual = false)
|
||||
{
|
||||
var rel = Path.GetRelativePath(parentPath.EnsureTrailingSlash(), subPath);
|
||||
return (rel != "." || allowEqual)
|
||||
&& rel != ".."
|
||||
&& !rel.StartsWith("../")
|
||||
&& !rel.StartsWith(@"..\")
|
||||
&& !Path.IsPathRooted(rel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns path ended with "\"
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static string EnsureTrailingSlash(this string path)
|
||||
{
|
||||
return path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
53
Flow.Launcher.Test/FilesFoldersTest.cs
Normal file
53
Flow.Launcher.Test/FilesFoldersTest.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Flow.Launcher.Test
|
||||
{
|
||||
[TestFixture]
|
||||
|
||||
public class FilesFoldersTest
|
||||
{
|
||||
// Testcases from https://stackoverflow.com/a/31941905/20703207
|
||||
// Disk
|
||||
[TestCase(@"c:", @"c:\foo", true)]
|
||||
[TestCase(@"c:\", @"c:\foo", true)]
|
||||
// Slash
|
||||
[TestCase(@"c:\foo\bar\", @"c:\foo\", false)]
|
||||
[TestCase(@"c:\foo\bar", @"c:\foo\", false)]
|
||||
[TestCase(@"c:\foo", @"c:\foo\bar", true)]
|
||||
[TestCase(@"c:\foo\", @"c:\foo\bar", true)]
|
||||
// File
|
||||
[TestCase(@"c:\foo", @"c:\foo\a.txt", true)]
|
||||
[TestCase(@"c:\foo", @"c:/foo/a.txt", true)]
|
||||
[TestCase(@"c:\FOO\a.txt", @"c:\foo", false)]
|
||||
[TestCase(@"c:\foo\a.txt", @"c:\foo\", false)]
|
||||
[TestCase(@"c:\foobar\a.txt", @"c:\foo", false)]
|
||||
[TestCase(@"c:\foobar\a.txt", @"c:\foo\", false)]
|
||||
[TestCase(@"c:\foo\", @"c:\foo.txt", false)]
|
||||
// Prefix
|
||||
[TestCase(@"c:\foo", @"c:\foobar", false)]
|
||||
[TestCase(@"C:\Program", @"C:\Program Files\", false)]
|
||||
[TestCase(@"c:\foobar", @"c:\foo\a.txt", false)]
|
||||
[TestCase(@"c:\foobar\", @"c:\foo\a.txt", false)]
|
||||
// Edge case
|
||||
[TestCase(@"c:\foo", @"c:\foo\..\bar\baz", false)]
|
||||
[TestCase(@"c:\bar", @"c:\foo\..\bar\baz", true)]
|
||||
[TestCase(@"c:\barr", @"c:\foo\..\bar\baz", false)]
|
||||
// Equality
|
||||
[TestCase(@"c:\foo", @"c:\foo", false)]
|
||||
[TestCase(@"c:\foo\", @"c:\foo", false)]
|
||||
[TestCase(@"c:\foo", @"c:\foo\", false)]
|
||||
public void GivenTwoPaths_WhenCheckPathContains_ThenShouldBeExpectedResult(string parentPath, string path, bool expectedResult)
|
||||
{
|
||||
Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path));
|
||||
}
|
||||
|
||||
[TestCase(@"c:\foo", @"c:\foo", true)]
|
||||
[TestCase(@"c:\foo\", @"c:\foo", true)]
|
||||
[TestCase(@"c:\foo", @"c:\foo\", true)]
|
||||
public void GivenTwoPathsAreTheSame_WhenCheckPathContains_ThenShouldBeTrue(string parentPath, string path, bool expectedResult)
|
||||
{
|
||||
Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -48,13 +48,13 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" Version="4.16.1" />
|
||||
<PackageReference Include="Moq" Version="4.18.4" />
|
||||
<PackageReference Include="nunit" Version="3.13.3" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -7,9 +7,11 @@ using Flow.Launcher.Plugin.SharedCommands;
|
|||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using static Flow.Launcher.Plugin.Explorer.Search.SearchManager;
|
||||
|
||||
namespace Flow.Launcher.Test.Plugins
|
||||
{
|
||||
|
|
@ -176,7 +178,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
var searchManager = new SearchManager(new Settings(), new PluginInitContext());
|
||||
|
||||
// When
|
||||
var result = SearchManager.IsFileContentSearch(query.ActionKeyword);
|
||||
var result = searchManager.IsFileContentSearch(query.ActionKeyword);
|
||||
|
||||
// Then
|
||||
Assert.IsTrue(result,
|
||||
|
|
@ -193,6 +195,7 @@ namespace Flow.Launcher.Test.Plugins
|
|||
[TestCase(@"c:\>*", true)]
|
||||
[TestCase(@"c:\>", true)]
|
||||
[TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)]
|
||||
[TestCase(@"c:\SomeLocation\SomeOtherLocation", true)]
|
||||
public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult)
|
||||
{
|
||||
// When, Given
|
||||
|
|
@ -393,5 +396,68 @@ namespace Flow.Launcher.Test.Plugins
|
|||
// Then
|
||||
Assert.AreEqual(result, expectedResult);
|
||||
}
|
||||
|
||||
[TestCase(@"c:\foo", @"c:\foo", true)]
|
||||
[TestCase(@"C:\Foo\", @"c:\foo\", true)]
|
||||
[TestCase(@"c:\foo", @"c:\foo\", false)]
|
||||
public void GivenTwoPaths_WhenCompared_ThenShouldBeExpectedSameOrDifferent(string path1, string path2, bool expectedResult)
|
||||
{
|
||||
// Given
|
||||
var comparator = PathEqualityComparator.Instance;
|
||||
var result1 = new Result
|
||||
{
|
||||
Title = Path.GetFileName(path1),
|
||||
SubTitle = path1
|
||||
};
|
||||
var result2 = new Result
|
||||
{
|
||||
Title = Path.GetFileName(path2),
|
||||
SubTitle = path2
|
||||
};
|
||||
|
||||
// When, Then
|
||||
Assert.AreEqual(expectedResult, comparator.Equals(result1, result2));
|
||||
}
|
||||
|
||||
[TestCase(@"c:\foo\", @"c:\foo\")]
|
||||
[TestCase(@"C:\Foo\", @"c:\foo\")]
|
||||
public void GivenTwoPaths_WhenComparedHasCode_ThenShouldBeSame(string path1, string path2)
|
||||
{
|
||||
// Given
|
||||
var comparator = PathEqualityComparator.Instance;
|
||||
var result1 = new Result
|
||||
{
|
||||
Title = Path.GetFileName(path1),
|
||||
SubTitle = path1
|
||||
};
|
||||
var result2 = new Result
|
||||
{
|
||||
Title = Path.GetFileName(path2),
|
||||
SubTitle = path2
|
||||
};
|
||||
|
||||
var hash1 = comparator.GetHashCode(result1);
|
||||
var hash2 = comparator.GetHashCode(result2);
|
||||
|
||||
// When, Then
|
||||
Assert.IsTrue(hash1 == hash2);
|
||||
}
|
||||
|
||||
[TestCase(@"%appdata%", true)]
|
||||
[TestCase(@"%appdata%\123", true)]
|
||||
[TestCase(@"c:\foo %appdata%\", false)]
|
||||
[TestCase(@"c:\users\%USERNAME%\downloads", true)]
|
||||
[TestCase(@"c:\downloads", false)]
|
||||
[TestCase(@"%", false)]
|
||||
[TestCase(@"%%", false)]
|
||||
[TestCase(@"%bla%blabla%", false)]
|
||||
public void GivenPath_WhenHavingEnvironmentVariableOrNot_ThenShouldBeExpected(string path, bool expectedResult)
|
||||
{
|
||||
// When
|
||||
var result = EnvironmentVariables.HasEnvironmentVar(path);
|
||||
|
||||
// Then
|
||||
Assert.AreEqual(result, expectedResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
using System.Windows;
|
||||
|
|
@ -84,14 +85,14 @@ namespace Flow.Launcher
|
|||
|
||||
Current.MainWindow = window;
|
||||
Current.MainWindow.Title = Constant.FlowLauncher;
|
||||
|
||||
|
||||
HotKeyMapper.Initialize(_mainVM);
|
||||
|
||||
// happlebao todo temp fix for instance code logic
|
||||
// todo temp fix for instance code logic
|
||||
// load plugin before change language, because plugin language also needs be changed
|
||||
InternationalizationManager.Instance.Settings = _settings;
|
||||
InternationalizationManager.Instance.ChangeLanguage(_settings.Language);
|
||||
// main windows needs initialized before theme change because of blur settigns
|
||||
// main windows needs initialized before theme change because of blur settings
|
||||
ThemeManager.Instance.Settings = _settings;
|
||||
ThemeManager.Instance.ChangeTheme(_settings.Theme);
|
||||
|
||||
|
|
@ -130,20 +131,17 @@ namespace Flow.Launcher
|
|||
//[Conditional("RELEASE")]
|
||||
private void AutoUpdates()
|
||||
{
|
||||
Task.Run(async () =>
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
if (_settings.AutoUpdates)
|
||||
{
|
||||
// check udpate every 5 hours
|
||||
var timer = new Timer(1000 * 60 * 60 * 5);
|
||||
timer.Elapsed += async (s, e) =>
|
||||
{
|
||||
await _updater.UpdateAppAsync(API);
|
||||
};
|
||||
timer.Start();
|
||||
|
||||
// check updates on startup
|
||||
// check update every 5 hours
|
||||
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
|
||||
await _updater.UpdateAppAsync(API);
|
||||
|
||||
while (await timer.WaitForNextTickAsync())
|
||||
// check updates on startup
|
||||
await _updater.UpdateAppAsync(API);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
25
Flow.Launcher/Converters/DiameterToCenterPointConverter.cs
Normal file
25
Flow.Launcher/Converters/DiameterToCenterPointConverter.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Flow.Launcher.Converters
|
||||
{
|
||||
public class DiameterToCenterPointConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is double d)
|
||||
{
|
||||
return new Point(d / 2, d / 2);
|
||||
}
|
||||
|
||||
return new Point(0, 0);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Flow.Launcher/Converters/IconRadiusConverter.cs
Normal file
27
Flow.Launcher/Converters/IconRadiusConverter.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using Windows.Devices.PointOfService;
|
||||
|
||||
namespace Flow.Launcher.Converters
|
||||
{
|
||||
public class IconRadiusConverter : IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Length != 2)
|
||||
throw new ArgumentException("IconRadiusConverter must have 2 parameters");
|
||||
|
||||
return values[1] switch
|
||||
{
|
||||
true => (double)values[0] / 2,
|
||||
false => (double)values[0],
|
||||
_ => throw new ArgumentException("The second argument should be boolean", nameof(values))
|
||||
};
|
||||
}
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
</PackageReference>
|
||||
<PackageReference Include="InputSimulator" Version="1.0.4" />
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||
<PackageReference Include="ModernWpfUI" Version="0.9.4" />
|
||||
<PackageReference Include="ModernWpfUI" Version="0.9.6" />
|
||||
<PackageReference Include="NHotkey.Wpf" Version="2.1.0" />
|
||||
<PackageReference Include="NuGet.CommandLine" Version="6.3.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
Margin="0,0,18,0"
|
||||
VerticalContentAlignment="Center"
|
||||
input:InputMethod.IsInputMethodEnabled="False"
|
||||
GotFocus="tbHotkey_GotFocus"
|
||||
LostFocus="tbHotkey_LostFocus"
|
||||
PreviewKeyDown="TbHotkey_OnPreviewKeyDown"
|
||||
TabIndex="100" />
|
||||
|
|
|
|||
|
|
@ -9,16 +9,11 @@ using Flow.Launcher.Helper;
|
|||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Plugin;
|
||||
using System.Threading;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace Flow.Launcher
|
||||
{
|
||||
public partial class HotkeyControl : UserControl
|
||||
{
|
||||
private Brush tbMsgForegroundColorOriginal;
|
||||
|
||||
private string tbMsgTextOriginal;
|
||||
|
||||
public HotkeyModel CurrentHotkey { get; private set; }
|
||||
public bool CurrentHotkeyAvailable { get; private set; }
|
||||
|
||||
|
|
@ -29,8 +24,6 @@ namespace Flow.Launcher
|
|||
public HotkeyControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
tbMsgTextOriginal = tbMsg.Text;
|
||||
tbMsgForegroundColorOriginal = tbMsg.Foreground;
|
||||
}
|
||||
|
||||
private CancellationTokenSource hotkeyUpdateSource;
|
||||
|
|
@ -55,9 +48,7 @@ namespace Flow.Launcher
|
|||
specialKeyState.CtrlPressed,
|
||||
key);
|
||||
|
||||
var hotkeyString = hotkeyModel.ToString();
|
||||
|
||||
if (hotkeyString == tbHotkey.Text)
|
||||
if (hotkeyModel.Equals(CurrentHotkey))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -72,33 +63,32 @@ namespace Flow.Launcher
|
|||
|
||||
public async Task SetHotkeyAsync(HotkeyModel keyModel, bool triggerValidate = true)
|
||||
{
|
||||
CurrentHotkey = keyModel;
|
||||
|
||||
tbHotkey.Text = CurrentHotkey.ToString();
|
||||
tbHotkey.Text = keyModel.ToString();
|
||||
tbHotkey.Select(tbHotkey.Text.Length, 0);
|
||||
|
||||
if (triggerValidate)
|
||||
{
|
||||
CurrentHotkeyAvailable = CheckHotkeyAvailability();
|
||||
if (!CurrentHotkeyAvailable)
|
||||
{
|
||||
tbMsg.Foreground = new SolidColorBrush(Colors.Red);
|
||||
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable");
|
||||
}
|
||||
else
|
||||
{
|
||||
tbMsg.Foreground = new SolidColorBrush(Colors.Green);
|
||||
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success");
|
||||
}
|
||||
tbMsg.Visibility = Visibility.Visible;
|
||||
bool hotkeyAvailable = CheckHotkeyAvailability(keyModel);
|
||||
CurrentHotkeyAvailable = hotkeyAvailable;
|
||||
SetMessage(hotkeyAvailable);
|
||||
OnHotkeyChanged();
|
||||
|
||||
var token = hotkeyUpdateSource.Token;
|
||||
await Task.Delay(500, token);
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), null);
|
||||
Keyboard.ClearFocus();
|
||||
|
||||
if (CurrentHotkeyAvailable)
|
||||
{
|
||||
CurrentHotkey = keyModel;
|
||||
// To trigger LostFocus
|
||||
FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), null);
|
||||
Keyboard.ClearFocus();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentHotkey = keyModel;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,14 +97,40 @@ namespace Flow.Launcher
|
|||
return SetHotkeyAsync(new HotkeyModel(keyStr), triggerValidate);
|
||||
}
|
||||
|
||||
private bool CheckHotkeyAvailability() => HotKeyMapper.CheckAvailability(CurrentHotkey);
|
||||
private static bool CheckHotkeyAvailability(HotkeyModel hotkey) => hotkey.Validate() && HotKeyMapper.CheckAvailability(hotkey);
|
||||
|
||||
public new bool IsFocused => tbHotkey.IsFocused;
|
||||
|
||||
private void tbHotkey_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
tbMsg.Text = tbMsgTextOriginal;
|
||||
tbHotkey.Text = CurrentHotkey?.ToString() ?? "";
|
||||
tbHotkey.Select(tbHotkey.Text.Length, 0);
|
||||
}
|
||||
|
||||
private void tbHotkey_GotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ResetMessage();
|
||||
}
|
||||
|
||||
private void ResetMessage()
|
||||
{
|
||||
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("flowlauncherPressHotkey");
|
||||
tbMsg.SetResourceReference(TextBox.ForegroundProperty, "Color05B");
|
||||
}
|
||||
|
||||
private void SetMessage(bool hotkeyAvailable)
|
||||
{
|
||||
if (!hotkeyAvailable)
|
||||
{
|
||||
tbMsg.Foreground = new SolidColorBrush(Colors.Red);
|
||||
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable");
|
||||
}
|
||||
else
|
||||
{
|
||||
tbMsg.Foreground = new SolidColorBrush(Colors.Green);
|
||||
tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success");
|
||||
}
|
||||
tbMsg.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,11 +9,11 @@
|
|||
xmlns:svgc="http://sharpvectors.codeplex.com/svgc/"
|
||||
xmlns:ui="http://schemas.modernwpf.com/2019"
|
||||
xmlns:vm="clr-namespace:Flow.Launcher.ViewModel"
|
||||
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
|
||||
Name="FlowMainWindow"
|
||||
Title="Flow Launcher"
|
||||
MinWidth="{Binding MainWindowWidth, Mode=OneWay}"
|
||||
MaxWidth="{Binding MainWindowWidth, Mode=OneWay}"
|
||||
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
|
||||
AllowDrop="True"
|
||||
AllowsTransparency="True"
|
||||
Background="Transparent"
|
||||
|
|
@ -38,9 +38,9 @@
|
|||
<converters:QuerySuggestionBoxConverter x:Key="QuerySuggestionBoxConverter" />
|
||||
<converters:BorderClipConverter x:Key="BorderClipConverter" />
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolToIMEConversionModeConverter x:Key="BoolToIMEConversionModeConverter"/>
|
||||
<converters:BoolToIMEStateConverter x:Key="BoolToIMEStateConverter"/>
|
||||
<converters:StringToKeyBindingConverter x:Key="StringToKeyBindingConverter"/>
|
||||
<converters:BoolToIMEConversionModeConverter x:Key="BoolToIMEConversionModeConverter" />
|
||||
<converters:BoolToIMEStateConverter x:Key="BoolToIMEStateConverter" />
|
||||
<converters:StringToKeyBindingConverter x:Key="StringToKeyBindingConverter" />
|
||||
</Window.Resources>
|
||||
<Window.InputBindings>
|
||||
<KeyBinding Key="Escape" Command="{Binding EscCommand}" />
|
||||
|
|
@ -180,11 +180,11 @@
|
|||
<KeyBinding
|
||||
Key="F12"
|
||||
Command="{Binding ToggleGameModeCommand}"
|
||||
Modifiers="Ctrl"/>
|
||||
Modifiers="Ctrl" />
|
||||
<KeyBinding
|
||||
Key="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='key'}"
|
||||
Command="{Binding TogglePreviewCommand}"
|
||||
Modifiers="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}"/>
|
||||
Modifiers="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" />
|
||||
</Window.InputBindings>
|
||||
<Grid>
|
||||
<Border MouseDown="OnMouseDown" Style="{DynamicResource WindowBorderStyle}">
|
||||
|
|
@ -207,12 +207,12 @@
|
|||
<TextBox
|
||||
x:Name="QueryTextBox"
|
||||
AllowDrop="True"
|
||||
InputMethod.PreferredImeConversionMode="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEConversionModeConverter}}"
|
||||
InputMethod.PreferredImeState="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEStateConverter}}"
|
||||
PreviewDragOver="OnPreviewDragOver"
|
||||
PreviewKeyUp="QueryTextBox_KeyUp"
|
||||
Style="{DynamicResource QueryBoxStyle}"
|
||||
Text="{Binding QueryText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
InputMethod.PreferredImeConversionMode="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEConversionModeConverter}}"
|
||||
InputMethod.PreferredImeState="{Binding StartWithEnglishMode, Converter={StaticResource BoolToIMEStateConverter}}"
|
||||
Visibility="Visible">
|
||||
<TextBox.CommandBindings>
|
||||
<CommandBinding Command="ApplicationCommands.Copy" Executed="OnCopy" />
|
||||
|
|
@ -273,9 +273,6 @@
|
|||
<Grid>
|
||||
<Image
|
||||
x:Name="PluginActivationIcon"
|
||||
Width="32"
|
||||
Height="32"
|
||||
Margin="0,0,18,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
Panel.ZIndex="2"
|
||||
|
|
@ -403,10 +400,10 @@
|
|||
VerticalAlignment="Stretch"
|
||||
Style="{DynamicResource PreviewArea}"
|
||||
Visibility="{Binding PreviewVisible, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Border
|
||||
Style="{DynamicResource PreviewBorderStyle}"
|
||||
<Border
|
||||
d:DataContext="{d:DesignInstance vm:ResultViewModel}"
|
||||
DataContext="{Binding SelectedItem, ElementName=ResultListBox}"
|
||||
Style="{DynamicResource PreviewBorderStyle}"
|
||||
Visibility="{Binding ShowDefaultPreview}">
|
||||
<Grid
|
||||
Margin="20,0,10,0"
|
||||
|
|
@ -478,10 +475,10 @@
|
|||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border
|
||||
<Border
|
||||
d:DataContext="{d:DesignInstance vm:ResultViewModel}"
|
||||
DataContext="{Binding SelectedItem, ElementName=ResultListBox}"
|
||||
Style="{DynamicResource PreviewBorderStyle}"
|
||||
Style="{DynamicResource PreviewBorderStyle}"
|
||||
Visibility="{Binding ShowCustomizedPreview}">
|
||||
<ContentControl Content="{Binding Result.PreviewPanel.Value}" />
|
||||
</Border>
|
||||
|
|
|
|||
|
|
@ -485,7 +485,7 @@ namespace Flow.Launcher
|
|||
if (_settings.UseAnimation)
|
||||
await Task.Delay(100);
|
||||
|
||||
if (_settings.HideWhenDeactive)
|
||||
if (_settings.HideWhenDeactivated)
|
||||
{
|
||||
_viewModel.Hide();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,8 @@
|
|||
<ui:NumberBox
|
||||
x:Name="tbAction"
|
||||
Width="200"
|
||||
Maximum="100"
|
||||
Minimum="-100"
|
||||
Margin="10,0,15,0"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ namespace Flow.Launcher.Resources.Pages
|
|||
tbMsgTextOriginal = HotkeyControl.tbMsg.Text;
|
||||
tbMsgForegroundColorOriginal = HotkeyControl.tbMsg.Foreground;
|
||||
|
||||
HotkeyControl.SetHotkeyAsync(new Infrastructure.Hotkey.HotkeyModel(Settings.Hotkey), false);
|
||||
HotkeyControl.SetHotkeyAsync(Settings.Hotkey, false);
|
||||
}
|
||||
private void HotkeyControl_OnGotFocus(object sender, RoutedEventArgs args)
|
||||
{
|
||||
|
|
@ -49,4 +49,4 @@ namespace Flow.Launcher.Resources.Pages
|
|||
HotkeyControl.tbMsg.Foreground = tbMsgForegroundColorOriginal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,10 +27,14 @@
|
|||
Style="{DynamicResource BaseListboxStyle}"
|
||||
VirtualizingStackPanel.IsVirtualizing="True"
|
||||
VirtualizingStackPanel.VirtualizationMode="Standard"
|
||||
Visibility="{Binding Visbility}"
|
||||
Visibility="{Binding Visibility}"
|
||||
mc:Ignorable="d">
|
||||
<!-- IsSynchronizedWithCurrentItem: http://stackoverflow.com/a/7833798/2833083 -->
|
||||
|
||||
<ListBox.Resources>
|
||||
<converter:IconRadiusConverter x:Key="IconRadiusConverter" />
|
||||
<converter:DiameterToCenterPointConverter x:Key="DiameterToCenterPointConverter" />
|
||||
</ListBox.Resources>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button HorizontalAlignment="Stretch">
|
||||
|
|
@ -52,7 +56,7 @@
|
|||
<converter:OpenResultHotkeyVisibilityConverter x:Key="OpenResultHotkeyVisibilityConverter" />
|
||||
</Grid.Resources>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="60" />
|
||||
<ColumnDefinition Style="{DynamicResource ImageAreaWidth}" />
|
||||
<ColumnDefinition Width="9*" />
|
||||
<ColumnDefinition Width="Auto" MinWidth="8" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
|
@ -99,20 +103,29 @@
|
|||
BorderThickness="1">
|
||||
<Image
|
||||
x:Name="ImageIcon"
|
||||
Width="{Binding IconXY}"
|
||||
Height="{Binding IconXY}"
|
||||
Margin="0,0,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
IsHitTestVisible="False"
|
||||
RenderOptions.BitmapScalingMode="Fant"
|
||||
Source="{Binding Image, TargetNullValue={x:Null}}"
|
||||
Stretch="Uniform"
|
||||
Style="{DynamicResource ImageIconStyle}"
|
||||
Visibility="{Binding ShowIcon}">
|
||||
<Image.Clip>
|
||||
<EllipseGeometry
|
||||
Center="16 16"
|
||||
RadiusX="{Binding IconRadius}"
|
||||
RadiusY="{Binding IconRadius}" />
|
||||
<EllipseGeometry Center="{Binding ElementName=ImageIcon, Path=ActualWidth, Converter={StaticResource DiameterToCenterPointConverter}}">
|
||||
<EllipseGeometry.RadiusX>
|
||||
<MultiBinding Converter="{StaticResource IconRadiusConverter}">
|
||||
<Binding ElementName="ImageIcon" Path="ActualWidth" />
|
||||
<Binding Path="Result.RoundedIcon" />
|
||||
</MultiBinding>
|
||||
</EllipseGeometry.RadiusX>
|
||||
<EllipseGeometry.RadiusY>
|
||||
<MultiBinding Converter="{StaticResource IconRadiusConverter}">
|
||||
<Binding ElementName="ImageIcon" Path="ActualWidth" />
|
||||
<Binding Path="Result.RoundedIcon" />
|
||||
</MultiBinding>
|
||||
</EllipseGeometry.RadiusY>
|
||||
</EllipseGeometry>
|
||||
</Image.Clip>
|
||||
</Image>
|
||||
</Border>
|
||||
|
|
@ -196,7 +209,6 @@
|
|||
<Setter TargetName="Title" Property="Style" Value="{DynamicResource ItemTitleSelectedStyle}" />
|
||||
<Setter TargetName="SubTitle" Property="Style" Value="{DynamicResource ItemSubTitleSelectedStyle}" />
|
||||
<Setter TargetName="Hotkey" Property="Style" Value="{DynamicResource ItemHotkeySelectedStyle}" />
|
||||
<Setter TargetName="ImageIcon" Property="Style" Value="{DynamicResource ItemImageSelectedStyle}" />
|
||||
<Setter TargetName="GlyphIcon" Property="Style" Value="{DynamicResource ItemGlyphSelectedStyle}" />
|
||||
</DataTrigger>
|
||||
</DataTemplate.Triggers>
|
||||
|
|
@ -207,7 +219,7 @@
|
|||
<Style TargetType="{x:Type ListBoxItem}">
|
||||
<EventSetter Event="MouseEnter" Handler="OnMouseEnter" />
|
||||
<EventSetter Event="MouseMove" Handler="OnMouseMove" />
|
||||
<Setter Property="Height" Value="52" />
|
||||
<Setter Property="Height" Value="{DynamicResource ResultItemHeight}" />
|
||||
<Setter Property="Margin" Value="0" />
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
|
|
@ -240,4 +252,4 @@
|
|||
</Setter>
|
||||
</Style>
|
||||
</ListBox.ItemContainerStyle>
|
||||
</ListBox>
|
||||
</ListBox>
|
||||
|
|
@ -673,7 +673,7 @@
|
|||
<ui:ToggleSwitch
|
||||
Grid.Column="2"
|
||||
FocusVisualMargin="5"
|
||||
IsOn="{Binding Settings.HideWhenDeactive}"
|
||||
IsOn="{Binding Settings.HideWhenDeactivated}"
|
||||
Style="{DynamicResource SideToggleSwitch}" />
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
|
|
@ -1821,7 +1821,7 @@
|
|||
</Border.Clip>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="54" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
|
@ -2438,7 +2438,7 @@
|
|||
<Border Margin="0,8,0,0" Style="{DynamicResource SettingGroupBox}">
|
||||
<ItemsControl Style="{StaticResource SettingGrid}">
|
||||
<StackPanel Style="{StaticResource TextPanel}">
|
||||
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource previewHotkey}"/>
|
||||
<TextBlock Style="{DynamicResource SettingTitleLabel}" Text="{DynamicResource previewHotkey}" />
|
||||
<TextBlock Style="{DynamicResource SettingSubTitleLabel}" Text="{DynamicResource previewHotkeyToolTip}" />
|
||||
</StackPanel>
|
||||
<flowlauncher:HotkeyControl
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@
|
|||
<CornerRadius x:Key="ItemRadius">0</CornerRadius>
|
||||
<Thickness x:Key="ItemMargin">0</Thickness>
|
||||
<Thickness x:Key="ResultMargin">0</Thickness>
|
||||
<!-- Further font customisations are dynamically loaded in Theme.cs -->
|
||||
<!-- Further font customizations are dynamically loaded in Theme.cs -->
|
||||
|
||||
<system:Double x:Key="ResultItemHeight">52</system:Double>
|
||||
|
||||
<Style x:Key="BaseBulletStyle" TargetType="{x:Type Border}" />
|
||||
<Style
|
||||
|
|
@ -17,6 +19,10 @@
|
|||
x:Key="ItemBulletSelectedStyle"
|
||||
BasedOn="{StaticResource BaseBulletStyle}"
|
||||
TargetType="{x:Type Border}" />
|
||||
<Style x:Key="ImageIconStyle" TargetType="{x:Type Image}">
|
||||
<Setter Property="Height" Value="32" />
|
||||
<Setter Property="Width" Value="32" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="BaseQueryBoxStyle" TargetType="{x:Type TextBox}">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
|
|
@ -301,7 +307,7 @@
|
|||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
|
||||
<!-- Scrollbar in thr rigth of ScrollViewer -->
|
||||
<!-- Scrollbar in the right of ScrollViewer -->
|
||||
<ScrollBar
|
||||
x:Name="PART_VerticalScrollBar"
|
||||
Grid.Row="0"
|
||||
|
|
@ -385,6 +391,12 @@
|
|||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="Foreground" Value="#8f8f8f" />
|
||||
</Style>
|
||||
<Style x:Key="BaseItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="Foreground" Value="#8f8f8f" />
|
||||
</Style>
|
||||
|
||||
<!-- DO NOT USE THIS KEY. this key for themes with wrong typo. This key should be removed. Right key is BaseItemHotkeySelectedStyle. -->
|
||||
<Style x:Key="BaseItemHotkeySelecetedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="Foreground" Value="#8f8f8f" />
|
||||
|
|
@ -425,6 +437,9 @@
|
|||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style x:Key="ImageAreaWidth" TargetType="{x:Type ColumnDefinition}">
|
||||
<Setter Property="Width" Value="60" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="PreviewGlyph"
|
||||
BasedOn="{StaticResource BasePreviewGlyph}"
|
||||
|
|
@ -464,6 +479,14 @@
|
|||
</Style.Triggers>
|
||||
</Style>
|
||||
<!-- for classic themes -->
|
||||
|
||||
<Style x:Key="PluginActivationIcon" TargetType="{x:Type Image}">
|
||||
<Setter Property="Width" Value="32" />
|
||||
<Setter Property="Height" Value="32" />
|
||||
<Setter Property="Margin" Value="0,0,18,0" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SearchIconStyle" TargetType="{x:Type Path}">
|
||||
<Setter Property="Fill" Value="#555555" />
|
||||
<Setter Property="Width" Value="32" />
|
||||
|
|
@ -492,7 +515,7 @@
|
|||
</Style>
|
||||
<Style
|
||||
x:Key="ItemHotkeySelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelecetedStyle}"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="15" />
|
||||
<Setter Property="Foreground" Value="#8f8f8f" />
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@
|
|||
</Style>
|
||||
<Style
|
||||
x:Key="ItemHotkeySelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelecetedStyle}"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Foreground" Value="#ff79c6" />
|
||||
|
|
|
|||
217
Flow.Launcher/Themes/SlimLight.xaml
Normal file
217
Flow.Launcher/Themes/SlimLight.xaml
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:system="clr-namespace:System;assembly=mscorlib">
|
||||
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="pack://application:,,,/Themes/Base.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<system:Double x:Key="ResultItemHeight">38</system:Double>
|
||||
<Style x:Key="ImageAreaWidth" TargetType="{x:Type ColumnDefinition}">
|
||||
<Setter Property="Width" Value="46" />
|
||||
</Style>
|
||||
<Style x:Key="ImageIconStyle" TargetType="{x:Type Image}">
|
||||
<Setter Property="Height" Value="24" />
|
||||
<Setter Property="Width" Value="24" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ItemGlyph"
|
||||
BasedOn="{StaticResource BaseGlyphStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="24" />
|
||||
<Setter Property="Width" Value="24" />
|
||||
<Setter Property="Height" Value="24" />
|
||||
<Setter Property="Foreground" Value="#100f0f" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="QueryBoxStyle"
|
||||
BasedOn="{StaticResource BaseQueryBoxStyle}"
|
||||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Padding" Value="0,2,42,0" />
|
||||
<Setter Property="Foreground" Value="#282728" />
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="Height" Value="24" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="QuerySuggestionBoxStyle"
|
||||
BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}"
|
||||
TargetType="{x:Type TextBox}">
|
||||
<Setter Property="Padding" Value="0,2,42,0" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Height" Value="24" />
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="Foreground" Value="#8f8c8f" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="WindowBorderStyle"
|
||||
BasedOn="{StaticResource BaseWindowBorderStyle}"
|
||||
TargetType="{x:Type Border}">
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="BorderBrush" Value="#bcbabd" />
|
||||
<Setter Property="Background" Value="#edebee" />
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
<Setter Property="UseLayoutRounding" Value="True" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="WindowStyle"
|
||||
BasedOn="{StaticResource BaseWindowStyle}"
|
||||
TargetType="{x:Type Window}" />
|
||||
<Style
|
||||
x:Key="PendingLineStyle"
|
||||
BasedOn="{StaticResource BasePendingLineStyle}"
|
||||
TargetType="{x:Type Line}" />
|
||||
|
||||
<!-- Item Style -->
|
||||
<Style
|
||||
x:Key="ItemTitleStyle"
|
||||
BasedOn="{StaticResource BaseItemTitleStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Foreground" Value="#100f0f" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ItemSubTitleStyle"
|
||||
BasedOn="{StaticResource BaseItemSubTitleStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="10" />
|
||||
<Setter Property="Foreground" Value="#8f8d90" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="SeparatorStyle"
|
||||
BasedOn="{StaticResource BaseSeparatorStyle}"
|
||||
TargetType="{x:Type Rectangle}">
|
||||
<Setter Property="Fill" Value="#dedcde" />
|
||||
<Setter Property="Height" Value="1" />
|
||||
<Setter Property="Margin" Value="0,0,0,4" />
|
||||
</Style>
|
||||
<Style x:Key="HighlightStyle" />
|
||||
<Style
|
||||
x:Key="ItemTitleSelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemTitleSelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Foreground" Value="#100f0f" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ItemSubTitleSelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemSubTitleSelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="10" />
|
||||
<Setter Property="Foreground" Value="#8f8d90" />
|
||||
</Style>
|
||||
<SolidColorBrush x:Key="ItemSelectedBackgroundColor">#d6d4d7</SolidColorBrush>
|
||||
|
||||
<!-- button style in the middle of the scrollbar -->
|
||||
<Style
|
||||
x:Key="ThumbStyle"
|
||||
BasedOn="{StaticResource BaseThumbStyle}"
|
||||
TargetType="{x:Type Thumb}">
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type Thumb}">
|
||||
<Border
|
||||
Width="4"
|
||||
Margin="0,0,2,0"
|
||||
Background="#878687"
|
||||
BorderBrush="Transparent"
|
||||
BorderThickness="0"
|
||||
CornerRadius="2"
|
||||
DockPanel.Dock="Right" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ScrollBarStyle"
|
||||
BasedOn="{StaticResource BaseScrollBarStyle}"
|
||||
TargetType="{x:Type ScrollBar}" />
|
||||
<Style
|
||||
x:Key="SearchIconStyle"
|
||||
BasedOn="{StaticResource BaseSearchIconStyle}"
|
||||
TargetType="{x:Type Path}">
|
||||
<Setter Property="Fill" Value="#d5d3d6" />
|
||||
<Setter Property="Width" Value="24" />
|
||||
<Setter Property="Height" Value="24" />
|
||||
</Style>
|
||||
<Style x:Key="SearchIconPosition" TargetType="{x:Type Canvas}">
|
||||
<Setter Property="Background" Value="#edebee" />
|
||||
<Setter Property="Width" Value="24" />
|
||||
<Setter Property="Height" Value="24" />
|
||||
<Setter Property="Margin" Value="0,0,8,0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
</Style>
|
||||
<Style x:Key="PluginActivationIcon" TargetType="{x:Type Image}">
|
||||
<Setter Property="Width" Value="24" />
|
||||
<Setter Property="Height" Value="24" />
|
||||
<Setter Property="Margin" Value="0,0,8,0" />
|
||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ItemHotkeyStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="#8f8d90" />
|
||||
</Style>
|
||||
<Style x:Key="ItemHotkeySelectedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="Foreground" Value="#8f8d90" />
|
||||
</Style>
|
||||
<Style x:Key="ItemGlyphSelectedStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#100f0f" />
|
||||
<Setter Property="Width" Value="24" />
|
||||
<Setter Property="Height" Value="24" />
|
||||
<Setter Property="FontSize" Value="24" />
|
||||
</Style>
|
||||
<CornerRadius x:Key="ItemRadius">6</CornerRadius>
|
||||
<Thickness x:Key="ItemMargin">4 0 4 0</Thickness>
|
||||
<Thickness x:Key="ResultMargin">0 0 0 4</Thickness>
|
||||
<Style
|
||||
x:Key="ClockPanel"
|
||||
BasedOn="{StaticResource ClockPanel}"
|
||||
TargetType="{x:Type StackPanel}">
|
||||
<Setter Property="Orientation" Value="Horizontal" />
|
||||
<Setter Property="Margin" Value="0,0,42,0" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="ClockBox"
|
||||
BasedOn="{StaticResource BaseClockBox}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Foreground" Value="#8f8d90" />
|
||||
<Setter Property="Margin" Value="0,0,10,0" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="DateBox"
|
||||
BasedOn="{StaticResource BaseDateBox}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Foreground" Value="#8f8d90" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="PreviewBorderStyle"
|
||||
BasedOn="{StaticResource BasePreviewBorderStyle}"
|
||||
TargetType="{x:Type Border}">
|
||||
<Setter Property="Margin" Value="0,0,10,8" />
|
||||
<Setter Property="BorderBrush" Value="#dedcde" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="PreviewItemTitleStyle"
|
||||
BasedOn="{StaticResource BasePreviewItemTitleStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#100f0f" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="PreviewItemSubTitleStyle"
|
||||
BasedOn="{StaticResource BasePreviewItemSubTitleStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#8f8d90" />
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="PreviewGlyph"
|
||||
BasedOn="{StaticResource BasePreviewGlyph}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Foreground" Value="#100f0f" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
|
|
@ -120,7 +120,7 @@
|
|||
</Style>
|
||||
<Style
|
||||
x:Key="ItemHotkeySelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelecetedStyle}"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Foreground" Value="#acacac" />
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@
|
|||
</Style>
|
||||
<Style
|
||||
x:Key="ItemHotkeySelectedStyle"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelecetedStyle}"
|
||||
BasedOn="{StaticResource BaseItemHotkeySelectedStyle}"
|
||||
TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource HotkeySelectedForeground}" />
|
||||
|
|
|
|||
|
|
@ -535,13 +535,13 @@ namespace Flow.Launcher.ViewModel
|
|||
_selectedResults = value;
|
||||
if (SelectedIsFromQueryResults())
|
||||
{
|
||||
ContextMenu.Visbility = Visibility.Collapsed;
|
||||
History.Visbility = Visibility.Collapsed;
|
||||
ContextMenu.Visibility = Visibility.Collapsed;
|
||||
History.Visibility = Visibility.Collapsed;
|
||||
ChangeQueryText(_queryTextBeforeLeaveResults);
|
||||
}
|
||||
else
|
||||
{
|
||||
Results.Visbility = Visibility.Collapsed;
|
||||
Results.Visibility = Visibility.Collapsed;
|
||||
_queryTextBeforeLeaveResults = QueryText;
|
||||
|
||||
|
||||
|
|
@ -559,7 +559,7 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
_selectedResults.Visbility = Visibility.Visible;
|
||||
_selectedResults.Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -710,7 +710,7 @@ namespace Flow.Launcher.ViewModel
|
|||
if (query == null) // shortcut expanded
|
||||
{
|
||||
Results.Clear();
|
||||
Results.Visbility = Visibility.Collapsed;
|
||||
Results.Visibility = Visibility.Collapsed;
|
||||
PluginIconPath = null;
|
||||
SearchIconVisibility = Visibility.Visible;
|
||||
return;
|
||||
|
|
@ -841,15 +841,20 @@ namespace Flow.Launcher.ViewModel
|
|||
queryBuilder.Replace('@' + shortcut.Key, shortcut.Expand());
|
||||
}
|
||||
|
||||
string customExpanded = queryBuilder.ToString();
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
foreach (var shortcut in builtInShortcuts)
|
||||
{
|
||||
try
|
||||
{
|
||||
var expansion = shortcut.Expand();
|
||||
queryBuilder.Replace(shortcut.Key, expansion);
|
||||
queryBuilderTmp.Replace(shortcut.Key, expansion);
|
||||
if (customExpanded.Contains(shortcut.Key))
|
||||
{
|
||||
var expansion = shortcut.Expand();
|
||||
queryBuilder.Replace(shortcut.Key, expansion);
|
||||
queryBuilderTmp.Replace(shortcut.Key, expansion);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -43,13 +43,13 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
#region Properties
|
||||
|
||||
public int MaxHeight => MaxResults * 52;
|
||||
public double MaxHeight => MaxResults * (double)Application.Current.FindResource("ResultItemHeight")!;
|
||||
|
||||
public int SelectedIndex { get; set; }
|
||||
|
||||
public ResultViewModel SelectedItem { get; set; }
|
||||
public Thickness Margin { get; set; }
|
||||
public Visibility Visbility { get; set; } = Visibility.Collapsed;
|
||||
public Visibility Visibility { get; set; } = Visibility.Collapsed;
|
||||
|
||||
public ICommand RightClickResultCommand { get; init; }
|
||||
public ICommand LeftClickResultCommand { get; init; }
|
||||
|
|
@ -167,14 +167,14 @@ namespace Flow.Launcher.ViewModel
|
|||
SelectedItem = Results[0];
|
||||
}
|
||||
|
||||
switch (Visbility)
|
||||
switch (Visibility)
|
||||
{
|
||||
case Visibility.Collapsed when Results.Count > 0:
|
||||
SelectedIndex = 0;
|
||||
Visbility = Visibility.Visible;
|
||||
Visibility = Visibility.Visible;
|
||||
break;
|
||||
case Visibility.Visible when Results.Count == 0:
|
||||
Visbility = Visibility.Collapsed;
|
||||
Visibility = Visibility.Collapsed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -259,7 +259,7 @@ namespace Flow.Launcher.ViewModel
|
|||
return;
|
||||
|
||||
// manually update event
|
||||
// wpf use directx / double buffered already, so just reset all won't cause ui flickering
|
||||
// wpf use DirectX / double buffered already, so just reset all won't cause ui flickering
|
||||
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
|
||||
}
|
||||
private void AddAll(List<ResultViewModel> Items)
|
||||
|
|
|
|||
|
|
@ -325,18 +325,14 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
public IList<PluginViewModel> PluginViewModels
|
||||
{
|
||||
get
|
||||
{
|
||||
var metadatas = PluginManager.AllPlugins
|
||||
.OrderBy(x => x.Metadata.Disabled)
|
||||
.ThenBy(y => y.Metadata.Name)
|
||||
.Select(p => new PluginViewModel
|
||||
{
|
||||
PluginPair = p
|
||||
})
|
||||
.ToList();
|
||||
return metadatas;
|
||||
}
|
||||
get => PluginManager.AllPlugins
|
||||
.OrderBy(x => x.Metadata.Disabled)
|
||||
.ThenBy(y => y.Metadata.Name)
|
||||
.Select(p => new PluginViewModel
|
||||
{
|
||||
PluginPair = p
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public IList<PluginStoreItemViewModel> ExternalPlugins
|
||||
|
|
@ -407,7 +403,6 @@ namespace Flow.Launcher.ViewModel
|
|||
get { return Settings.Theme; }
|
||||
set
|
||||
{
|
||||
Settings.Theme = value;
|
||||
ThemeManager.Instance.ChangeTheme(value);
|
||||
|
||||
if (ThemeManager.Instance.BlurEnabled && Settings.UseDropShadowEffect)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Flow.Launcher.Plugin.Explorer.Search.IProvider;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Exceptions;
|
||||
|
||||
|
|
@ -20,7 +18,7 @@ public class EngineNotAvailableException : Exception
|
|||
string engineName,
|
||||
string resolution,
|
||||
string message,
|
||||
Func<ActionContext, ValueTask<bool>> action = null) : base(message)
|
||||
Func<ActionContext, ValueTask<bool>>? action = null) : base(message)
|
||||
{
|
||||
EngineName = engineName;
|
||||
Resolution = resolution;
|
||||
|
|
@ -40,6 +38,23 @@ public class EngineNotAvailableException : Exception
|
|||
EngineName = engineName;
|
||||
Resolution = resolution;
|
||||
}
|
||||
|
||||
public EngineNotAvailableException(
|
||||
string engineName,
|
||||
string resolution,
|
||||
string message,
|
||||
string errorIconPath,
|
||||
Func<ActionContext, ValueTask<bool>>? action = null) : base(message)
|
||||
{
|
||||
EngineName = engineName;
|
||||
Resolution = resolution;
|
||||
ErrorIcon = errorIconPath;
|
||||
Action = action ?? (_ =>
|
||||
{
|
||||
Clipboard.SetDataObject(this.ToString());
|
||||
return ValueTask.FromResult(true);
|
||||
});
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Data.OleDb" Version="5.0.0" />
|
||||
<PackageReference Include="System.Data.OleDb" Version="7.0.0" />
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageReference Include="tlbimp-Microsoft.Search.Interop" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<system:String x:Key="plugin_explorer_delete_folder_link">Are you sure you want to delete {0}?</system:String>
|
||||
<system:String x:Key="plugin_explorer_deletefolderconfirm">Are you sure you want to permanently delete this folder?</system:String>
|
||||
<system:String x:Key="plugin_explorer_deletefileconfirm">Are you sure you want to permanently delete this file?</system:String>
|
||||
<system:String x:Key="plugin_explorer_deletefilefolderconfirm">Are you sure you want to permanently delete this file/folder?</system:String>
|
||||
<system:String x:Key="plugin_explorer_deletefilefoldersuccess">Deletion successful</system:String>
|
||||
<system:String x:Key="plugin_explorer_deletefilefoldersuccess_detail">Successfully deleted {0}</system:String>
|
||||
<system:String x:Key="plugin_explorer_globalActionKeywordInvalid">Assigning the global action keyword could bring up too many results during search. Please choose a specific action keyword</system:String>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
internal const string DefaultContentSearchActionKeyword = "doc:";
|
||||
|
||||
internal const char DirectorySeperator = '\\';
|
||||
internal const char DirectorySeparator = '\\';
|
||||
|
||||
internal const string WindowsIndexingOptions = "srchadmin.dll";
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
var criteria = ConstructSearchCriteria(search);
|
||||
|
||||
if (search.LastIndexOf(Constants.AllFilesFolderSearchWildcard) >
|
||||
search.LastIndexOf(Constants.DirectorySeperator))
|
||||
search.LastIndexOf(Constants.DirectorySeparator))
|
||||
return DirectorySearch(new EnumerationOptions
|
||||
{
|
||||
RecurseSubdirectories = true
|
||||
|
|
@ -29,9 +29,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search.DirectoryInfo
|
|||
{
|
||||
string incompleteName = "";
|
||||
|
||||
if (!search.EndsWith(Constants.DirectorySeperator))
|
||||
if (!search.EndsWith(Constants.DirectorySeparator))
|
||||
{
|
||||
var indexOfSeparator = search.LastIndexOf(Constants.DirectorySeperator);
|
||||
var indexOfSeparator = search.LastIndexOf(Constants.DirectorySeparator);
|
||||
|
||||
incompleteName = search[(indexOfSeparator + 1)..].ToLower();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,70 +1,75 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using Flow.Launcher.Plugin.SharedCommands;
|
||||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search
|
||||
{
|
||||
public static class EnvironmentVariables
|
||||
{
|
||||
internal static bool IsEnvironmentVariableSearch(string search)
|
||||
private static Dictionary<string, string> _envStringPaths = null;
|
||||
private static Dictionary<string, string> EnvStringPaths
|
||||
{
|
||||
return search.StartsWith("%")
|
||||
&& search != "%%"
|
||||
&& !search.Contains("\\") &&
|
||||
LoadEnvironmentStringPaths().Count > 0;
|
||||
get
|
||||
{
|
||||
if (_envStringPaths == null)
|
||||
{
|
||||
LoadEnvironmentStringPaths();
|
||||
}
|
||||
return _envStringPaths;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Dictionary<string, string> LoadEnvironmentStringPaths()
|
||||
internal static bool IsEnvironmentVariableSearch(string search)
|
||||
{
|
||||
var envStringPaths = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
return search.StartsWith("%")
|
||||
&& search != "%%"
|
||||
&& !search.Contains('\\')
|
||||
&& EnvStringPaths.Count > 0;
|
||||
}
|
||||
|
||||
public static bool HasEnvironmentVar(string search)
|
||||
{
|
||||
// "c:\foo %appdata%\" returns false
|
||||
var splited = search.Split(Path.DirectorySeparatorChar);
|
||||
return splited.Any(dir => dir.StartsWith('%') &&
|
||||
dir.EndsWith('%') &&
|
||||
dir.Length > 2 &&
|
||||
dir.Split('%').Length == 3);
|
||||
}
|
||||
|
||||
private static void LoadEnvironmentStringPaths()
|
||||
{
|
||||
_envStringPaths = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
var homedrive = Environment.GetEnvironmentVariable("HOMEDRIVE")?.EnsureTrailingSlash() ?? "C:\\";
|
||||
|
||||
foreach (DictionaryEntry special in Environment.GetEnvironmentVariables())
|
||||
{
|
||||
var path = special.Value.ToString();
|
||||
// we add a trailing slash to the path to make sure drive paths become valid absolute paths.
|
||||
// for example, if %systemdrive% is C: we turn it to C:\
|
||||
path = path.EnsureTrailingSlash();
|
||||
|
||||
// if we don't have an absolute path, we use Path.GetFullPath to get one.
|
||||
// for example, if %homepath% is \Users\John we turn it to C:\Users\John
|
||||
// Add basepath for GetFullPath() to parse %HOMEPATH% correctly
|
||||
path = Path.IsPathFullyQualified(path) ? path : Path.GetFullPath(path, homedrive);
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
// we add a trailing slash to the path to make sure drive paths become valid absolute paths.
|
||||
// for example, if %systemdrive% is C: we turn it to C:\
|
||||
path = path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||
|
||||
// if we don't have an absolute path, we use Path.GetFullPath to get one.
|
||||
// for example, if %homepath% is \Users\John we turn it to C:\Users\John
|
||||
path = Path.IsPathFullyQualified(path) ? path : Path.GetFullPath(path);
|
||||
|
||||
// Variables are returned with a mixture of all upper/lower case.
|
||||
// Call ToLower() to make the results look consistent
|
||||
envStringPaths.Add(special.Key.ToString().ToLower(), path);
|
||||
// Call ToUpper() to make the results look consistent
|
||||
_envStringPaths.Add(special.Key.ToString().ToUpper(), path);
|
||||
}
|
||||
}
|
||||
|
||||
return envStringPaths;
|
||||
}
|
||||
|
||||
internal static string TranslateEnvironmentVariablePath(string environmentVariablePath)
|
||||
{
|
||||
var envStringPaths = LoadEnvironmentStringPaths();
|
||||
var splitSearch = environmentVariablePath.Substring(1).Split("%");
|
||||
var exactEnvStringPath = splitSearch[0];
|
||||
|
||||
// if there are more than 2 % characters in the query, don't bother
|
||||
if (splitSearch.Length == 2 && envStringPaths.ContainsKey(exactEnvStringPath))
|
||||
{
|
||||
var queryPartToReplace = $"%{exactEnvStringPath}%";
|
||||
var expandedPath = envStringPaths[exactEnvStringPath];
|
||||
// replace the %envstring% part of the query with its expanded equivalent
|
||||
return environmentVariablePath.Replace(queryPartToReplace, expandedPath);
|
||||
}
|
||||
|
||||
return environmentVariablePath;
|
||||
}
|
||||
|
||||
internal static List<Result> GetEnvironmentStringPathSuggestions(string querySearch, Query query, PluginInitContext context)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
|
||||
var environmentVariables = LoadEnvironmentStringPaths();
|
||||
var search = querySearch;
|
||||
|
||||
if (querySearch.EndsWith("%") && search.Length > 1)
|
||||
|
|
@ -72,12 +77,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
// query starts and ends with a %, find an exact match from env-string paths
|
||||
search = querySearch.Substring(1, search.Length - 2);
|
||||
|
||||
if (environmentVariables.ContainsKey(search))
|
||||
if (EnvStringPaths.ContainsKey(search))
|
||||
{
|
||||
var expandedPath = environmentVariables[search];
|
||||
|
||||
var expandedPath = EnvStringPaths[search];
|
||||
|
||||
results.Add(ResultManager.CreateFolderResult($"%{search}%", expandedPath, expandedPath, query));
|
||||
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
|
@ -90,8 +95,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
{
|
||||
search = search.Substring(1);
|
||||
}
|
||||
|
||||
foreach (var p in environmentVariables)
|
||||
|
||||
foreach (var p in EnvStringPaths)
|
||||
{
|
||||
if (p.Key.StartsWith(search, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,13 +13,12 @@ using Flow.Launcher.Plugin.Explorer.Exceptions;
|
|||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
||||
{
|
||||
|
||||
public static class EverythingApi
|
||||
{
|
||||
|
||||
private const int BufferSize = 4096;
|
||||
|
||||
private static SemaphoreSlim _semaphore = new(1, 1);
|
||||
|
||||
// cached buffer to remove redundant allocations.
|
||||
private static readonly StringBuilder buffer = new(BufferSize);
|
||||
|
||||
|
|
@ -35,46 +34,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
|||
InvalidCallError
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [match path].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [match path]; otherwise, <c>false</c>.</value>
|
||||
public static bool MatchPath
|
||||
{
|
||||
get => EverythingApiDllImport.Everything_GetMatchPath();
|
||||
set => EverythingApiDllImport.Everything_SetMatchPath(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [match case].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [match case]; otherwise, <c>false</c>.</value>
|
||||
public static bool MatchCase
|
||||
{
|
||||
get => EverythingApiDllImport.Everything_GetMatchCase();
|
||||
set => EverythingApiDllImport.Everything_SetMatchCase(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [match whole word].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [match whole word]; otherwise, <c>false</c>.</value>
|
||||
public static bool MatchWholeWord
|
||||
{
|
||||
get => EverythingApiDllImport.Everything_GetMatchWholeWord();
|
||||
set => EverythingApiDllImport.Everything_SetMatchWholeWord(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether [enable regex].
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if [enable regex]; otherwise, <c>false</c>.</value>
|
||||
public static bool EnableRegex
|
||||
{
|
||||
get => EverythingApiDllImport.Everything_GetRegex();
|
||||
set => EverythingApiDllImport.Everything_SetRegex(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the sort option is Fast Sort.
|
||||
/// </summary>
|
||||
|
|
@ -95,7 +54,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
|||
|
||||
try
|
||||
{
|
||||
EverythingApiDllImport.Everything_GetMajorVersion();
|
||||
EverythingApiDllImport.Everything_GetMajorVersion();
|
||||
var result = EverythingApiDllImport.Everything_GetLastError() != StateCode.IPCError;
|
||||
return result;
|
||||
}
|
||||
|
|
@ -122,7 +81,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
|||
|
||||
await _semaphore.WaitAsync(token);
|
||||
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
|
|
@ -152,6 +111,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
|||
EverythingApiDllImport.Everything_SetMax(option.MaxCount);
|
||||
|
||||
EverythingApiDllImport.Everything_SetSort(option.SortOption);
|
||||
EverythingApiDllImport.Everything_SetMatchPath(option.IsFullPathSearch);
|
||||
|
||||
if (token.IsCancellationRequested) yield break;
|
||||
|
||||
|
|
|
|||
|
|
@ -27,20 +27,16 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
|||
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_click_to_launch_or_install"),
|
||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_is_not_running"),
|
||||
ClickToInstallEverythingAsync)
|
||||
{
|
||||
ErrorIcon = Constants.EverythingErrorImagePath
|
||||
};
|
||||
Constants.EverythingErrorImagePath,
|
||||
ClickToInstallEverythingAsync);
|
||||
}
|
||||
catch (DllNotFoundException)
|
||||
{
|
||||
throw new EngineNotAvailableException(
|
||||
Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
||||
"Please check whether your system is x86 or x64",
|
||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue"))
|
||||
{
|
||||
ErrorIcon = Constants.GeneralSearchErrorImagePath
|
||||
};
|
||||
Constants.GeneralSearchErrorImagePath,
|
||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_sdk_issue"));
|
||||
}
|
||||
}
|
||||
private async ValueTask<bool> ClickToInstallEverythingAsync(ActionContext _)
|
||||
|
|
@ -72,16 +68,14 @@ namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
|||
if (!Settings.EnableEverythingContentSearch)
|
||||
{
|
||||
throw new EngineNotAvailableException(Enum.GetName(Settings.IndexSearchEngineOption.Everything)!,
|
||||
"Click to Enable Everything Content Search (only applicable to Everything 1.5+ with indexed content)",
|
||||
"Everything Content Search is not enabled.",
|
||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search"),
|
||||
Main.Context.API.GetTranslation("flowlauncher_plugin_everything_enable_content_search_tips"),
|
||||
Constants.EverythingErrorImagePath,
|
||||
_ =>
|
||||
{
|
||||
Settings.EnableEverythingContentSearch = true;
|
||||
return ValueTask.FromResult(true);
|
||||
})
|
||||
{
|
||||
ErrorIcon = Constants.EverythingErrorImagePath
|
||||
};
|
||||
});
|
||||
}
|
||||
if (token.IsCancellationRequested)
|
||||
yield break;
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@ using Flow.Launcher.Plugin.Everything.Everything;
|
|||
|
||||
namespace Flow.Launcher.Plugin.Explorer.Search.Everything
|
||||
{
|
||||
public record struct EverythingSearchOption(string Keyword,
|
||||
public record struct EverythingSearchOption(
|
||||
string Keyword,
|
||||
SortOption SortOption,
|
||||
bool IsContentSearch = false,
|
||||
bool IsContentSearch = false,
|
||||
string ContentSearchKeyword = default,
|
||||
string ParentPath = default,
|
||||
bool IsRecursive = true,
|
||||
int Offset = 0,
|
||||
int MaxCount = 100);
|
||||
int Offset = 0,
|
||||
int MaxCount = 100,
|
||||
bool IsFullPathSearch = true
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks
|
|||
{
|
||||
get
|
||||
{
|
||||
var path = Path.EndsWith(Constants.DirectorySeperator) ? Path[0..^1] : Path;
|
||||
var path = Path.EndsWith(Constants.DirectorySeparator) ? Path[0..^1] : Path;
|
||||
|
||||
if (path.EndsWith(':'))
|
||||
return path[0..^1] + " Drive";
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
var usePathSearchActionKeyword = Settings.PathSearchKeywordEnabled && !Settings.SearchActionKeywordEnabled;
|
||||
|
||||
var pathSearchActionKeyword = Settings.PathSearchActionKeyword == Query.GlobalPluginWildcardSign
|
||||
? string.Empty
|
||||
var pathSearchActionKeyword = Settings.PathSearchActionKeyword == Query.GlobalPluginWildcardSign
|
||||
? string.Empty
|
||||
: $"{Settings.PathSearchActionKeyword} ";
|
||||
|
||||
var searchActionKeyword = Settings.SearchActionKeyword == Query.GlobalPluginWildcardSign
|
||||
|
|
@ -36,12 +36,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
: $"{Settings.SearchActionKeyword} ";
|
||||
|
||||
var keyword = usePathSearchActionKeyword ? pathSearchActionKeyword : searchActionKeyword;
|
||||
|
||||
|
||||
var formatted_path = path;
|
||||
|
||||
if (type == ResultType.Folder)
|
||||
// the seperator is needed so when navigating the folder structure contents of the folder are listed
|
||||
formatted_path = path.EndsWith(Constants.DirectorySeperator) ? path : path + Constants.DirectorySeperator;
|
||||
// the separator is needed so when navigating the folder structure contents of the folder are listed
|
||||
formatted_path = path.EndsWith(Constants.DirectorySeparator) ? path : path + Constants.DirectorySeparator;
|
||||
|
||||
return $"{keyword}{formatted_path}";
|
||||
}
|
||||
|
|
@ -49,8 +49,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
public static string GetAutoCompleteText(string title, Query query, string path, ResultType resultType)
|
||||
{
|
||||
return !Settings.PathSearchKeywordEnabled && !Settings.SearchActionKeywordEnabled
|
||||
? $"{query.ActionKeyword} {title}" // Only Quick Access action keyword is used in this scenario
|
||||
: GetPathWithActionKeyword(path, resultType, query.ActionKeyword);
|
||||
? $"{query.ActionKeyword} {title}" // Only Quick Access action keyword is used in this scenario
|
||||
: GetPathWithActionKeyword(path, resultType, query.ActionKeyword);
|
||||
}
|
||||
|
||||
public static Result CreateResult(Query query, SearchResult result)
|
||||
|
|
@ -71,7 +71,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
{
|
||||
Title = title,
|
||||
IcoPath = path,
|
||||
SubTitle = Path.GetDirectoryName(path),
|
||||
SubTitle = subtitle,
|
||||
AutoCompleteText = GetAutoCompleteText(title, query, path, ResultType.Folder),
|
||||
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData,
|
||||
CopyText = path,
|
||||
|
|
@ -187,9 +187,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
internal static Result CreateOpenCurrentFolderResult(string path, string actionKeyword, bool windowsIndexed = false)
|
||||
{
|
||||
// Path passed from PathSearchAsync ends with Constants.DirectorySeperator ('\'), need to remove the seperator
|
||||
// Path passed from PathSearchAsync ends with Constants.DirectorySeparator ('\'), need to remove the separator
|
||||
// so it's consistent with folder results returned by index search which does not end with one
|
||||
var folderPath = path.TrimEnd(Constants.DirectorySeperator);
|
||||
var folderPath = path.TrimEnd(Constants.DirectorySeparator);
|
||||
|
||||
return new Result
|
||||
{
|
||||
|
|
@ -215,9 +215,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
internal static Result CreateFileResult(string filePath, Query query, int score = 0, bool windowsIndexed = false)
|
||||
{
|
||||
Result.PreviewInfo preview = IsMedia(Path.GetExtension(filePath)) ? new Result.PreviewInfo {
|
||||
IsMedia = true,
|
||||
PreviewImagePath = filePath,
|
||||
Result.PreviewInfo preview = IsMedia(Path.GetExtension(filePath)) ? new Result.PreviewInfo
|
||||
{
|
||||
IsMedia = true, PreviewImagePath = filePath,
|
||||
} : Result.PreviewInfo.Default;
|
||||
|
||||
var title = Path.GetFileName(filePath);
|
||||
|
|
@ -246,6 +246,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
{
|
||||
FileName = filePath,
|
||||
UseShellExecute = true,
|
||||
WorkingDirectory = Settings.UseLocationAsWorkingDir ? Path.GetDirectoryName(filePath) : string.Empty,
|
||||
Verb = "runas",
|
||||
});
|
||||
}
|
||||
|
|
@ -286,8 +287,8 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
public static bool IsMedia(string extension)
|
||||
{
|
||||
if (string.IsNullOrEmpty(extension))
|
||||
{
|
||||
return false;
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -295,7 +296,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
}
|
||||
}
|
||||
|
||||
public static readonly string[] MediaExtensions = { ".jpg", ".png", ".avi", ".mkv", ".bmp", ".gif", ".wmv", ".mp3", ".flac", ".mp4" };
|
||||
public static readonly string[] MediaExtensions =
|
||||
{
|
||||
".jpg", ".png", ".avi", ".mkv", ".bmp", ".gif", ".wmv", ".mp3", ".flac", ".mp4"
|
||||
};
|
||||
}
|
||||
|
||||
public enum ResultType
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
{
|
||||
public class SearchManager
|
||||
{
|
||||
internal static PluginInitContext Context;
|
||||
internal PluginInitContext Context;
|
||||
|
||||
internal static Settings Settings;
|
||||
internal Settings Settings;
|
||||
|
||||
public SearchManager(Settings settings, PluginInitContext context)
|
||||
{
|
||||
|
|
@ -23,19 +23,23 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
Settings = settings;
|
||||
}
|
||||
|
||||
private class PathEqualityComparator : IEqualityComparer<Result>
|
||||
/// <summary>
|
||||
/// Note: A path that ends with "\" and one that doesn't will not be regarded as equal.
|
||||
/// </summary>
|
||||
public class PathEqualityComparator : IEqualityComparer<Result>
|
||||
{
|
||||
private static PathEqualityComparator instance;
|
||||
public static PathEqualityComparator Instance => instance ??= new PathEqualityComparator();
|
||||
|
||||
public bool Equals(Result x, Result y)
|
||||
{
|
||||
return x.Title == y.Title && x.SubTitle == y.SubTitle;
|
||||
return x.Title.Equals(y.Title, StringComparison.OrdinalIgnoreCase)
|
||||
&& string.Equals(x.SubTitle, y.SubTitle, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public int GetHashCode(Result obj)
|
||||
{
|
||||
return HashCode.Combine(obj.Title.GetHashCode(), obj.SubTitle?.GetHashCode() ?? 0);
|
||||
return HashCode.Combine(obj.Title.ToLowerInvariant(), obj.SubTitle?.ToLowerInvariant() ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,19 +109,21 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
await foreach (var search in searchResults.WithCancellation(token).ConfigureAwait(false))
|
||||
results.Add(ResultManager.CreateResult(query, search));
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return new List<Result>();
|
||||
}
|
||||
catch (EngineNotAvailableException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e is OperationCanceledException)
|
||||
return results.ToList();
|
||||
|
||||
if (e is EngineNotAvailableException)
|
||||
throw;
|
||||
|
||||
throw new SearchException(engineName, e.Message, e);
|
||||
}
|
||||
|
||||
|
||||
results.RemoveWhere(r => Settings.IndexSearchExcludedSubdirectoryPaths.Any(
|
||||
excludedPath => r.SubTitle.StartsWith(excludedPath.Path, StringComparison.OrdinalIgnoreCase)));
|
||||
excludedPath => FilesFolders.PathContains(excludedPath.Path, r.SubTitle)));
|
||||
|
||||
return results.ToList();
|
||||
}
|
||||
|
|
@ -142,7 +148,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
};
|
||||
}
|
||||
|
||||
private static List<Result> EverythingContentSearchResult(Query query)
|
||||
private List<Result> EverythingContentSearchResult(Query query)
|
||||
{
|
||||
return new List<Result>()
|
||||
{
|
||||
|
|
@ -167,18 +173,12 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
|
||||
var results = new HashSet<Result>(PathEqualityComparator.Instance);
|
||||
|
||||
var isEnvironmentVariable = EnvironmentVariables.IsEnvironmentVariableSearch(querySearch);
|
||||
|
||||
if (isEnvironmentVariable)
|
||||
if (EnvironmentVariables.IsEnvironmentVariableSearch(querySearch))
|
||||
return EnvironmentVariables.GetEnvironmentStringPathSuggestions(querySearch, query, Context);
|
||||
|
||||
// Query is a location path with a full environment variable, eg. %appdata%\somefolder\
|
||||
var isEnvironmentVariablePath = querySearch[1..].Contains("%\\");
|
||||
|
||||
var locationPath = querySearch;
|
||||
|
||||
if (isEnvironmentVariablePath)
|
||||
locationPath = EnvironmentVariables.TranslateEnvironmentVariablePath(locationPath);
|
||||
// Query is a location path with a full environment variable, eg. %appdata%\somefolder\, c:\users\%USERNAME%\downloads
|
||||
var needToExpand = EnvironmentVariables.HasEnvironmentVar(querySearch);
|
||||
var locationPath = needToExpand ? Environment.ExpandEnvironmentVariables(querySearch) : querySearch;
|
||||
|
||||
// Check that actual location exists, otherwise directory search will throw directory not found exception
|
||||
if (!FilesFolders.ReturnPreviousDirectoryIfIncompleteString(locationPath).LocationExists())
|
||||
|
|
@ -234,7 +234,7 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
return results.ToList();
|
||||
}
|
||||
|
||||
public static bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword;
|
||||
public bool IsFileContentSearch(string actionKeyword) => actionKeyword == Settings.FileContentSearchActionKeyword;
|
||||
|
||||
|
||||
private bool UseWindowsIndexForDirectorySearch(string locationPath)
|
||||
|
|
@ -245,10 +245,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search
|
|||
x => FilesFolders.ReturnPreviousDirectoryIfIncompleteString(pathToDirectory).StartsWith(x.Path, StringComparison.OrdinalIgnoreCase))
|
||||
&& WindowsIndex.WindowsIndex.PathIsIndexed(pathToDirectory);
|
||||
}
|
||||
|
||||
|
||||
internal static bool IsEnvironmentVariableSearch(string search)
|
||||
{
|
||||
return search.StartsWith("%")
|
||||
return search.StartsWith("%")
|
||||
&& search != "%%"
|
||||
&& !search.Contains('\\');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,27 +97,25 @@ namespace Flow.Launcher.Plugin.Explorer.Search.WindowsIndex
|
|||
|
||||
private IAsyncEnumerable<SearchResult> HandledEngineNotAvailableExceptionAsync()
|
||||
{
|
||||
if (!SearchManager.Settings.WarnWindowsSearchServiceOff)
|
||||
if (!Settings.WarnWindowsSearchServiceOff)
|
||||
return AsyncEnumerable.Empty<SearchResult>();
|
||||
|
||||
var api = SearchManager.Context.API;
|
||||
var api = Main.Context.API;
|
||||
|
||||
throw new EngineNotAvailableException(
|
||||
"Windows Index",
|
||||
api.GetTranslation("plugin_explorer_windowsSearchServiceFix"),
|
||||
api.GetTranslation("plugin_explorer_windowsSearchServiceNotRunning"),
|
||||
Constants.WindowsIndexErrorImagePath,
|
||||
c =>
|
||||
{
|
||||
SearchManager.Settings.WarnWindowsSearchServiceOff = false;
|
||||
Settings.WarnWindowsSearchServiceOff = false;
|
||||
|
||||
// Clears the warning message so user is not mistaken that it has not worked
|
||||
api.ChangeQuery(string.Empty);
|
||||
|
||||
return ValueTask.FromResult(false);
|
||||
})
|
||||
{
|
||||
ErrorIcon = Constants.WindowsIndexErrorImagePath
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
"Name": "Explorer",
|
||||
"Description": "Find and manage files and folders via Windows Search or Everything",
|
||||
"Author": "Jeremy Wu",
|
||||
"Version": "2.1.0",
|
||||
"Version": "2.2.0",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"ID": "6A122269676E40EB86EB543B945932B9",
|
||||
"ActionKeyword": "*",
|
||||
"ActionKeyword": "?",
|
||||
"Name": "Plugin Indicator",
|
||||
"Description": "Provides plugin action keyword suggestions",
|
||||
"Author": "qianlifeng",
|
||||
"Version": "2.0.1",
|
||||
"Version": "2.0.2",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.PluginIndicator.dll",
|
||||
|
|
|
|||
|
|
@ -38,6 +38,6 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.3" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
"Name": "Plugins Manager",
|
||||
"Description": "Management of installing, uninstalling or updating Flow Launcher plugins",
|
||||
"Author": "Jeremy Wu",
|
||||
"Version": "2.0.0",
|
||||
"Version": "2.0.1",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll",
|
||||
|
|
|
|||
|
|
@ -378,15 +378,10 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
MatchResult matchResult;
|
||||
|
||||
// We suppose Name won't be null
|
||||
if (!Main._settings.EnableDescription || Description == null || Name.StartsWith(Description))
|
||||
if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || Name.Equals(Description))
|
||||
{
|
||||
title = Name;
|
||||
matchResult = StringMatcher.FuzzySearch(query, title);
|
||||
}
|
||||
else if (Description.StartsWith(Name))
|
||||
{
|
||||
title = Description;
|
||||
matchResult = StringMatcher.FuzzySearch(query, Description);
|
||||
matchResult = StringMatcher.FuzzySearch(query, Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -401,15 +396,19 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
}
|
||||
matchResult = descriptionMatch;
|
||||
}
|
||||
else matchResult = nameMatch;
|
||||
else
|
||||
{
|
||||
matchResult = nameMatch;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchResult.Success)
|
||||
if (!matchResult.IsSearchPrecisionScoreMet())
|
||||
return null;
|
||||
|
||||
var result = new Result
|
||||
{
|
||||
Title = title,
|
||||
AutoCompleteText = Name,
|
||||
SubTitle = Main._settings.HideAppsPath ? string.Empty : Location,
|
||||
IcoPath = LogoPath,
|
||||
Preview = new Result.PreviewInfo
|
||||
|
|
|
|||
|
|
@ -90,44 +90,28 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
bool useLocalizedName = !string.IsNullOrEmpty(LocalizedName) && !Name.Equals(LocalizedName);
|
||||
string resultName = useLocalizedName ? LocalizedName : Name;
|
||||
|
||||
if (!Main._settings.EnableDescription)
|
||||
if (!Main._settings.EnableDescription || string.IsNullOrWhiteSpace(Description) || resultName.Equals(Description))
|
||||
{
|
||||
title = resultName;
|
||||
matchResult = StringMatcher.FuzzySearch(query, resultName);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(Description) || resultName.StartsWith(Description))
|
||||
// Search in both
|
||||
title = $"{resultName}: {Description}";
|
||||
var nameMatch = StringMatcher.FuzzySearch(query, resultName);
|
||||
var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
|
||||
if (descriptionMatch.Score > nameMatch.Score)
|
||||
{
|
||||
// Description is invalid or included in resultName
|
||||
// Description is always localized, so Name.StartsWith(Description) is generally useless
|
||||
title = resultName;
|
||||
matchResult = StringMatcher.FuzzySearch(query, resultName);
|
||||
}
|
||||
else if (Description.StartsWith(resultName))
|
||||
{
|
||||
// resultName included in Description
|
||||
title = Description;
|
||||
matchResult = StringMatcher.FuzzySearch(query, Description);
|
||||
for (int i = 0; i < descriptionMatch.MatchData.Count; i++)
|
||||
{
|
||||
descriptionMatch.MatchData[i] += resultName.Length + 2; // 2 is ": "
|
||||
}
|
||||
matchResult = descriptionMatch;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Search in both
|
||||
title = $"{resultName}: {Description}";
|
||||
var nameMatch = StringMatcher.FuzzySearch(query, resultName);
|
||||
var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
|
||||
if (descriptionMatch.Score > nameMatch.Score)
|
||||
{
|
||||
for (int i = 0; i < descriptionMatch.MatchData.Count; i++)
|
||||
{
|
||||
descriptionMatch.MatchData[i] += resultName.Length + 2; // 2 is ": "
|
||||
}
|
||||
matchResult = descriptionMatch;
|
||||
}
|
||||
else
|
||||
{
|
||||
matchResult = nameMatch;
|
||||
}
|
||||
matchResult = nameMatch;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -171,6 +155,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
var result = new Result
|
||||
{
|
||||
Title = title,
|
||||
AutoCompleteText = resultName,
|
||||
SubTitle = subtitle,
|
||||
IcoPath = IcoPath,
|
||||
Score = matchResult.Score,
|
||||
|
|
@ -486,8 +471,8 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
}
|
||||
|
||||
var paths = pathEnv.Split(";", StringSplitOptions.RemoveEmptyEntries).DistinctBy(p => p.ToLowerInvariant());
|
||||
|
||||
var toFilter = paths.Where(x => commonParents.All(parent => !IsSubPathOf(x, parent)))
|
||||
|
||||
var toFilter = paths.Where(x => commonParents.All(parent => !FilesFolders.PathContains(parent, x)))
|
||||
.AsParallel()
|
||||
.SelectMany(p => EnumerateProgramsInDir(p, suffixes, recursive: false));
|
||||
|
||||
|
|
@ -779,17 +764,6 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
}
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/66877016
|
||||
private static bool IsSubPathOf(string subPath, string basePath)
|
||||
{
|
||||
var rel = Path.GetRelativePath(basePath, subPath);
|
||||
return rel != "."
|
||||
&& rel != ".."
|
||||
&& !rel.StartsWith("../")
|
||||
&& !rel.StartsWith(@"..\")
|
||||
&& !Path.IsPathRooted(rel);
|
||||
}
|
||||
|
||||
private static List<string> GetCommonParents(IEnumerable<ProgramSource> programSources)
|
||||
{
|
||||
// To avoid unnecessary io
|
||||
|
|
@ -801,8 +775,7 @@ namespace Flow.Launcher.Plugin.Program.Programs
|
|||
HashSet<ProgramSource> parents = group.ToHashSet();
|
||||
foreach (var source in group)
|
||||
{
|
||||
if (parents.Any(p => IsSubPathOf(source.Location, p.Location) &&
|
||||
source != p))
|
||||
if (parents.Any(p => FilesFolders.PathContains(p.Location, source.Location)))
|
||||
{
|
||||
parents.Remove(source);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Name": "Program",
|
||||
"Description": "Search programs in Flow.Launcher",
|
||||
"Author": "qianlifeng",
|
||||
"Version": "2.1.0",
|
||||
"Version": "2.2.0",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Program.dll",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Name": "Shell",
|
||||
"Description": "Provide executing commands from Flow Launcher",
|
||||
"Author": "qianlifeng",
|
||||
"Version": "2.0.0",
|
||||
"Version": "2.0.1",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Shell.dll",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Name": "System Commands",
|
||||
"Description": "Provide System related commands. e.g. shutdown,lock, setting etc.",
|
||||
"Author": "qianlifeng",
|
||||
"Version": "2.0.0",
|
||||
"Version": "2.0.1",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Sys.dll",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Name": "URL",
|
||||
"Description": "Open the typed URL from Flow Launcher",
|
||||
"Author": "qianlifeng",
|
||||
"Version": "2.0.0",
|
||||
"Version": "2.0.1",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.Url.dll",
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
"Name": "Web Searches",
|
||||
"Description": "Provide the web search ability",
|
||||
"Author": "qianlifeng",
|
||||
"Version": "2.0.1",
|
||||
"Version": "2.0.2",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.WebSearch.dll",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"Description": "Search settings inside Control Panel and Settings App",
|
||||
"Name": "Windows Settings",
|
||||
"Author": "TobiasSekan",
|
||||
"Version": "3.0.1",
|
||||
"Version": "3.0.2",
|
||||
"Language": "csharp",
|
||||
"Website": "https://github.com/Flow-Launcher/Flow.Launcher",
|
||||
"ExecuteFileName": "Flow.Launcher.Plugin.WindowsSettings.dll",
|
||||
|
|
|
|||
|
|
@ -330,6 +330,8 @@ And you can download <a href="https://github.com/Flow-Launcher/Flow.Launcher/dis
|
|||
<a href="https://github.com/itsonlyfrans"><img src="https://avatars.githubusercontent.com/u/46535667?v=4" width="10%" /></a>
|
||||
|
||||
<a href="https://github.com/andreqramos"><img src="https://avatars.githubusercontent.com/u/49326063?v=4" width="10%" /></a>
|
||||
|
||||
<a href="https://github.com/patrickdobler"><img src="https://avatars.githubusercontent.com/u/16536946?v=4" width="10%" /></a>
|
||||
</p>
|
||||
|
||||
### Mentions
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
version: '1.11.0.{build}'
|
||||
version: '1.12.1.{build}'
|
||||
|
||||
init:
|
||||
- ps: |
|
||||
|
|
|
|||
Loading…
Reference in a new issue