whitespace forced changes (#6002)

This commit is contained in:
Clint Rutkas
2020-08-17 10:00:56 -07:00
committed by GitHub
parent 649e7e103d
commit d055ba1c3b
129 changed files with 14175 additions and 14175 deletions

View File

@@ -1,196 +1,196 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using Mages.Core;
using Wox.Infrastructure.Logger;
using Wox.Plugin;
namespace Microsoft.Plugin.Calculator
{
public class Main : IPlugin, IPluginI18n, IDisposable
{
private static readonly Regex RegValidExpressChar = new Regex(
@"^(" +
@"ceil|floor|exp|pi|e|max|min|det|abs|log|ln|sqrt|" +
@"sin|cos|tan|arcsin|arccos|arctan|" +
@"eigval|eigvec|eig|sum|polar|plot|round|sort|real|zeta|" +
@"bin2dec|hex2dec|oct2dec|" +
@"==|~=|&&|\|\||" +
@"[ei]|[0-9]|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" +
@")+$", RegexOptions.Compiled);
private static readonly Regex RegBrackets = new Regex(@"[\(\)\[\]]", RegexOptions.Compiled);
private static readonly Engine MagesEngine = new Engine();
private PluginInitContext Context { get; set; }
private string IconPath { get; set; }
private bool _disposed = false;
public List<Result> Query(Query query)
{
if (query == null)
{
throw new ArgumentNullException(paramName: nameof(query));
}
if (query.Search.Length <= 2 // don't affect when user only input "e" or "i" keyword
|| !RegValidExpressChar.IsMatch(query.Search)
|| !IsBracketComplete(query.Search))
{
return new List<Result>();
}
try
{
var result = MagesEngine.Interpret(query.Search);
// This could happen for some incorrect queries, like pi(2)
if (result == null)
{
return new List<Result>();
}
if (result.ToString() == "NaN")
{
result = Context.API.GetTranslation("wox_plugin_calculator_not_a_number");
}
if (result is Function)
{
result = Context.API.GetTranslation("wox_plugin_calculator_expression_not_complete");
}
if (!string.IsNullOrEmpty(result?.ToString()))
{
var roundedResult = Math.Round(Convert.ToDecimal(result, CultureInfo.CurrentCulture), 10, MidpointRounding.AwayFromZero);
return new List<Result>
{
new Result
{
Title = roundedResult.ToString(CultureInfo.CurrentCulture),
IcoPath = IconPath,
Score = 300,
SubTitle = Context.API.GetTranslation("wox_plugin_calculator_copy_number_to_clipboard"),
Action = c =>
{
var ret = false;
var thread = new Thread(() =>
{
try
{
Clipboard.SetText(result.ToString());
ret = true;
}
catch (ExternalException)
{
MessageBox.Show("Copy failed, please try later");
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
return ret;
},
},
};
}
} // We want to keep the process alive if any the mages library throws any exceptions.
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception e)
#pragma warning restore CA1031 // Do not catch general exception types
{
Log.Exception($"|Microsoft.Plugin.Calculator.Main.Query|Exception when query for <{query}>", e);
}
return new List<Result>();
}
private static bool IsBracketComplete(string query)
{
var matchs = RegBrackets.Matches(query);
var leftBracketCount = 0;
foreach (Match match in matchs)
{
if (match.Value == "(" || match.Value == "[")
{
leftBracketCount++;
}
else
{
leftBracketCount--;
}
}
return leftBracketCount == 0;
}
public void Init(PluginInitContext context)
{
if (context == null)
{
throw new ArgumentNullException(paramName: nameof(context));
}
Context = context;
Context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(Context.API.GetCurrentTheme());
}
// Todo : Update with theme based IconPath
private void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
IconPath = "Images/calculator.light.png";
}
else
{
IconPath = "Images/calculator.dark.png";
}
}
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
{
UpdateIconPath(newTheme);
}
public string GetTranslatedPluginTitle()
{
return Context.API.GetTranslation("wox_plugin_calculator_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return Context.API.GetTranslation("wox_plugin_calculator_plugin_description");
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
Context.API.ThemeChanged -= OnThemeChanged;
_disposed = true;
}
}
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows;
using Mages.Core;
using Wox.Infrastructure.Logger;
using Wox.Plugin;
namespace Microsoft.Plugin.Calculator
{
public class Main : IPlugin, IPluginI18n, IDisposable
{
private static readonly Regex RegValidExpressChar = new Regex(
@"^(" +
@"ceil|floor|exp|pi|e|max|min|det|abs|log|ln|sqrt|" +
@"sin|cos|tan|arcsin|arccos|arctan|" +
@"eigval|eigvec|eig|sum|polar|plot|round|sort|real|zeta|" +
@"bin2dec|hex2dec|oct2dec|" +
@"==|~=|&&|\|\||" +
@"[ei]|[0-9]|[\+\-\*\/\^\., ""]|[\(\)\|\!\[\]]" +
@")+$", RegexOptions.Compiled);
private static readonly Regex RegBrackets = new Regex(@"[\(\)\[\]]", RegexOptions.Compiled);
private static readonly Engine MagesEngine = new Engine();
private PluginInitContext Context { get; set; }
private string IconPath { get; set; }
private bool _disposed = false;
public List<Result> Query(Query query)
{
if (query == null)
{
throw new ArgumentNullException(paramName: nameof(query));
}
if (query.Search.Length <= 2 // don't affect when user only input "e" or "i" keyword
|| !RegValidExpressChar.IsMatch(query.Search)
|| !IsBracketComplete(query.Search))
{
return new List<Result>();
}
try
{
var result = MagesEngine.Interpret(query.Search);
// This could happen for some incorrect queries, like pi(2)
if (result == null)
{
return new List<Result>();
}
if (result.ToString() == "NaN")
{
result = Context.API.GetTranslation("wox_plugin_calculator_not_a_number");
}
if (result is Function)
{
result = Context.API.GetTranslation("wox_plugin_calculator_expression_not_complete");
}
if (!string.IsNullOrEmpty(result?.ToString()))
{
var roundedResult = Math.Round(Convert.ToDecimal(result, CultureInfo.CurrentCulture), 10, MidpointRounding.AwayFromZero);
return new List<Result>
{
new Result
{
Title = roundedResult.ToString(CultureInfo.CurrentCulture),
IcoPath = IconPath,
Score = 300,
SubTitle = Context.API.GetTranslation("wox_plugin_calculator_copy_number_to_clipboard"),
Action = c =>
{
var ret = false;
var thread = new Thread(() =>
{
try
{
Clipboard.SetText(result.ToString());
ret = true;
}
catch (ExternalException)
{
MessageBox.Show("Copy failed, please try later");
}
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
thread.Join();
return ret;
},
},
};
}
} // We want to keep the process alive if any the mages library throws any exceptions.
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception e)
#pragma warning restore CA1031 // Do not catch general exception types
{
Log.Exception($"|Microsoft.Plugin.Calculator.Main.Query|Exception when query for <{query}>", e);
}
return new List<Result>();
}
private static bool IsBracketComplete(string query)
{
var matchs = RegBrackets.Matches(query);
var leftBracketCount = 0;
foreach (Match match in matchs)
{
if (match.Value == "(" || match.Value == "[")
{
leftBracketCount++;
}
else
{
leftBracketCount--;
}
}
return leftBracketCount == 0;
}
public void Init(PluginInitContext context)
{
if (context == null)
{
throw new ArgumentNullException(paramName: nameof(context));
}
Context = context;
Context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(Context.API.GetCurrentTheme());
}
// Todo : Update with theme based IconPath
private void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
IconPath = "Images/calculator.light.png";
}
else
{
IconPath = "Images/calculator.dark.png";
}
}
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
{
UpdateIconPath(newTheme);
}
public string GetTranslatedPluginTitle()
{
return Context.API.GetTranslation("wox_plugin_calculator_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return Context.API.GetTranslation("wox_plugin_calculator_plugin_description");
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
Context.API.ThemeChanged -= OnThemeChanged;
_disposed = true;
}
}
}
}
}

View File

@@ -1,107 +1,107 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
namespace Microsoft.Plugin.Calculator
{
/// <summary>
/// Tries to convert all numbers in a text from one culture format to another.
/// </summary>
public class NumberTranslator
{
private readonly CultureInfo sourceCulture;
private readonly CultureInfo targetCulture;
private readonly Regex splitRegexForSource;
private readonly Regex splitRegexForTarget;
private NumberTranslator(CultureInfo sourceCulture, CultureInfo targetCulture)
{
this.sourceCulture = sourceCulture;
this.targetCulture = targetCulture;
splitRegexForSource = GetSplitRegex(this.sourceCulture);
splitRegexForTarget = GetSplitRegex(this.targetCulture);
}
/// <summary>
/// Create a new <see cref="NumberTranslator"/> - returns null if no number conversion
/// is required between the cultures.
/// </summary>
/// <param name="sourceCulture">source culture</param>
/// <param name="targetCulture">target culture</param>
/// <returns>Number translator for target culture</returns>
public static NumberTranslator Create(CultureInfo sourceCulture, CultureInfo targetCulture)
{
if (sourceCulture == null)
{
throw new ArgumentNullException(paramName: nameof(sourceCulture));
}
if (targetCulture == null)
{
throw new ArgumentNullException(paramName: nameof(sourceCulture));
}
bool conversionRequired = sourceCulture.NumberFormat.NumberDecimalSeparator != targetCulture.NumberFormat.NumberDecimalSeparator
|| sourceCulture.NumberFormat.PercentGroupSeparator != targetCulture.NumberFormat.PercentGroupSeparator
|| sourceCulture.NumberFormat.NumberGroupSizes != targetCulture.NumberFormat.NumberGroupSizes;
return conversionRequired
? new NumberTranslator(sourceCulture, targetCulture)
: null;
}
/// <summary>
/// Translate from source to target culture.
/// </summary>
/// <param name="input">input string to translate</param>
/// <returns>translated string</returns>
public string Translate(string input)
{
return Translate(input, sourceCulture, targetCulture, splitRegexForSource);
}
/// <summary>
/// Translate from target to source culture.
/// </summary>
/// <param name="input">input string to translate back to source culture</param>
/// <returns>source culture string</returns>
public string TranslateBack(string input)
{
return Translate(input, targetCulture, sourceCulture, splitRegexForTarget);
}
private static string Translate(string input, CultureInfo cultureFrom, CultureInfo cultureTo, Regex splitRegex)
{
var outputBuilder = new StringBuilder();
string[] tokens = splitRegex.Split(input);
foreach (string token in tokens)
{
decimal number;
outputBuilder.Append(
decimal.TryParse(token, NumberStyles.Number, cultureFrom, out number)
? number.ToString(cultureTo)
: token);
}
return outputBuilder.ToString();
}
private static Regex GetSplitRegex(CultureInfo culture)
{
var splitPattern = $"((?:\\d|{Regex.Escape(culture.NumberFormat.NumberDecimalSeparator)}";
if (!string.IsNullOrEmpty(culture.NumberFormat.NumberGroupSeparator))
{
splitPattern += $"|{Regex.Escape(culture.NumberFormat.NumberGroupSeparator)}";
}
splitPattern += ")+)";
return new Regex(splitPattern);
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
namespace Microsoft.Plugin.Calculator
{
/// <summary>
/// Tries to convert all numbers in a text from one culture format to another.
/// </summary>
public class NumberTranslator
{
private readonly CultureInfo sourceCulture;
private readonly CultureInfo targetCulture;
private readonly Regex splitRegexForSource;
private readonly Regex splitRegexForTarget;
private NumberTranslator(CultureInfo sourceCulture, CultureInfo targetCulture)
{
this.sourceCulture = sourceCulture;
this.targetCulture = targetCulture;
splitRegexForSource = GetSplitRegex(this.sourceCulture);
splitRegexForTarget = GetSplitRegex(this.targetCulture);
}
/// <summary>
/// Create a new <see cref="NumberTranslator"/> - returns null if no number conversion
/// is required between the cultures.
/// </summary>
/// <param name="sourceCulture">source culture</param>
/// <param name="targetCulture">target culture</param>
/// <returns>Number translator for target culture</returns>
public static NumberTranslator Create(CultureInfo sourceCulture, CultureInfo targetCulture)
{
if (sourceCulture == null)
{
throw new ArgumentNullException(paramName: nameof(sourceCulture));
}
if (targetCulture == null)
{
throw new ArgumentNullException(paramName: nameof(sourceCulture));
}
bool conversionRequired = sourceCulture.NumberFormat.NumberDecimalSeparator != targetCulture.NumberFormat.NumberDecimalSeparator
|| sourceCulture.NumberFormat.PercentGroupSeparator != targetCulture.NumberFormat.PercentGroupSeparator
|| sourceCulture.NumberFormat.NumberGroupSizes != targetCulture.NumberFormat.NumberGroupSizes;
return conversionRequired
? new NumberTranslator(sourceCulture, targetCulture)
: null;
}
/// <summary>
/// Translate from source to target culture.
/// </summary>
/// <param name="input">input string to translate</param>
/// <returns>translated string</returns>
public string Translate(string input)
{
return Translate(input, sourceCulture, targetCulture, splitRegexForSource);
}
/// <summary>
/// Translate from target to source culture.
/// </summary>
/// <param name="input">input string to translate back to source culture</param>
/// <returns>source culture string</returns>
public string TranslateBack(string input)
{
return Translate(input, targetCulture, sourceCulture, splitRegexForTarget);
}
private static string Translate(string input, CultureInfo cultureFrom, CultureInfo cultureTo, Regex splitRegex)
{
var outputBuilder = new StringBuilder();
string[] tokens = splitRegex.Split(input);
foreach (string token in tokens)
{
decimal number;
outputBuilder.Append(
decimal.TryParse(token, NumberStyles.Number, cultureFrom, out number)
? number.ToString(cultureTo)
: token);
}
return outputBuilder.ToString();
}
private static Regex GetSplitRegex(CultureInfo culture)
{
var splitPattern = $"((?:\\d|{Regex.Escape(culture.NumberFormat.NumberDecimalSeparator)}";
if (!string.IsNullOrEmpty(culture.NumberFormat.NumberGroupSeparator))
{
splitPattern += $"|{Regex.Escape(culture.NumberFormat.NumberGroupSeparator)}";
}
splitPattern += ")+)";
return new Regex(splitPattern);
}
}
}

View File

@@ -1,142 +1,142 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using Wox.Infrastructure;
using Wox.Infrastructure.Logger;
using Wox.Plugin;
namespace Microsoft.Plugin.Folder
{
internal class ContextMenuLoader : IContextMenu
{
private readonly PluginInitContext _context;
public ContextMenuLoader(PluginInitContext context)
{
_context = context;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log the exception")]
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
var contextMenus = new List<ContextMenuResult>();
if (selectedResult.ContextData is SearchResult record)
{
if (record.Type == ResultType.File)
{
contextMenus.Add(CreateOpenContainingFolderResult(record));
}
var icoPath = (record.Type == ResultType.File) ? Main.FileImagePath : Main.FolderImagePath;
contextMenus.Add(new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_folder_copy_path"),
Glyph = "\xE8C8",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.C,
AcceleratorModifiers = ModifierKeys.Control,
Action = (context) =>
{
try
{
Clipboard.SetText(record.FullPath);
return true;
}
catch (Exception e)
{
var message = "Fail to set text in clipboard";
LogException(message, e);
_context.API.ShowMsg(message);
return false;
}
},
});
contextMenus.Add(new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_folder_open_in_console"),
Glyph = "\xE756",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.C,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = (context) =>
{
try
{
if (record.Type == ResultType.File)
{
Helper.OpenInConsole(Path.GetDirectoryName(record.FullPath));
}
else
{
Helper.OpenInConsole(record.FullPath);
}
return true;
}
catch (Exception e)
{
Log.Exception($"|Microsoft.Plugin.Folder.ContextMenuLoader.LoadContextMenus| Failed to open {record.FullPath} in console, {e.Message}", e);
return false;
}
},
});
}
return contextMenus;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log the exception")]
private ContextMenuResult CreateOpenContainingFolderResult(SearchResult record)
{
return new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_folder_open_containing_folder"),
Glyph = "\xE838",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.E,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = _ =>
{
try
{
Process.Start("explorer.exe", $" /select,\"{record.FullPath}\"");
}
catch (Exception e)
{
var message = $"Fail to open file at {record.FullPath}";
LogException(message, e);
_context.API.ShowMsg(message);
return false;
}
return true;
},
};
}
public static void LogException(string message, Exception e)
{
Log.Exception($"|Microsoft.Plugin.Folder.ContextMenu|{message}", e);
}
}
public enum ResultType
{
Volume,
Folder,
File,
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Input;
using Wox.Infrastructure;
using Wox.Infrastructure.Logger;
using Wox.Plugin;
namespace Microsoft.Plugin.Folder
{
internal class ContextMenuLoader : IContextMenu
{
private readonly PluginInitContext _context;
public ContextMenuLoader(PluginInitContext context)
{
_context = context;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log the exception")]
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
var contextMenus = new List<ContextMenuResult>();
if (selectedResult.ContextData is SearchResult record)
{
if (record.Type == ResultType.File)
{
contextMenus.Add(CreateOpenContainingFolderResult(record));
}
var icoPath = (record.Type == ResultType.File) ? Main.FileImagePath : Main.FolderImagePath;
contextMenus.Add(new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_folder_copy_path"),
Glyph = "\xE8C8",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.C,
AcceleratorModifiers = ModifierKeys.Control,
Action = (context) =>
{
try
{
Clipboard.SetText(record.FullPath);
return true;
}
catch (Exception e)
{
var message = "Fail to set text in clipboard";
LogException(message, e);
_context.API.ShowMsg(message);
return false;
}
},
});
contextMenus.Add(new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_folder_open_in_console"),
Glyph = "\xE756",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.C,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = (context) =>
{
try
{
if (record.Type == ResultType.File)
{
Helper.OpenInConsole(Path.GetDirectoryName(record.FullPath));
}
else
{
Helper.OpenInConsole(record.FullPath);
}
return true;
}
catch (Exception e)
{
Log.Exception($"|Microsoft.Plugin.Folder.ContextMenuLoader.LoadContextMenus| Failed to open {record.FullPath} in console, {e.Message}", e);
return false;
}
},
});
}
return contextMenus;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log the exception")]
private ContextMenuResult CreateOpenContainingFolderResult(SearchResult record)
{
return new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_folder_open_containing_folder"),
Glyph = "\xE838",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.E,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = _ =>
{
try
{
Process.Start("explorer.exe", $" /select,\"{record.FullPath}\"");
}
catch (Exception e)
{
var message = $"Fail to open file at {record.FullPath}";
LogException(message, e);
_context.API.ShowMsg(message);
return false;
}
return true;
},
};
}
public static void LogException(string message, Exception e)
{
Log.Exception($"|Microsoft.Plugin.Folder.ContextMenu|{message}", e);
}
}
public enum ResultType
{
Volume,
Folder,
File,
}
}

View File

@@ -1,127 +1,127 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Forms;
using Wox.Plugin;
using DataFormats = System.Windows.DataFormats;
using DragDropEffects = System.Windows.DragDropEffects;
using DragEventArgs = System.Windows.DragEventArgs;
using MessageBox = System.Windows.MessageBox;
namespace Microsoft.Plugin.Folder
{
public partial class FileSystemSettings
{
private IPublicAPI _woxAPI;
private FolderSettings _settings;
public FileSystemSettings(IPublicAPI woxAPI, FolderSettings settings)
{
_woxAPI = woxAPI;
InitializeComponent();
_settings = settings ?? throw new ArgumentNullException(paramName: nameof(settings));
lbxFolders.ItemsSource = _settings.FolderLinks;
}
private void BtnDelete_Click(object sender, RoutedEventArgs e)
{
if (lbxFolders.SelectedItem is FolderLink selectedFolder)
{
string msg = string.Format(CultureInfo.InvariantCulture, _woxAPI.GetTranslation("wox_plugin_folder_delete_folder_link"), selectedFolder.Path);
if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
_settings.FolderLinks.Remove(selectedFolder);
lbxFolders.Items.Refresh();
}
}
else
{
string warning = _woxAPI.GetTranslation("wox_plugin_folder_select_folder_link_warning");
MessageBox.Show(warning);
}
}
private void BtnEdit_Click(object sender, RoutedEventArgs e)
{
if (lbxFolders.SelectedItem is FolderLink selectedFolder)
{
using (var folderBrowserDialog = new FolderBrowserDialog())
{
folderBrowserDialog.SelectedPath = selectedFolder.Path;
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
{
var link = _settings.FolderLinks.First(x => x.Path == selectedFolder.Path);
link.Path = folderBrowserDialog.SelectedPath;
}
lbxFolders.Items.Refresh();
}
}
else
{
string warning = _woxAPI.GetTranslation("wox_plugin_folder_select_folder_link_warning");
MessageBox.Show(warning);
}
}
private void BtnAdd_Click(object sender, RoutedEventArgs e)
{
using (var folderBrowserDialog = new FolderBrowserDialog())
{
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
{
var newFolder = new FolderLink
{
Path = folderBrowserDialog.SelectedPath,
};
_settings.FolderLinks.Add(newFolder);
}
lbxFolders.Items.Refresh();
}
}
private void LbxFolders_Drop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files != null && files.Any())
{
foreach (string s in files)
{
if (Directory.Exists(s))
{
var newFolder = new FolderLink
{
Path = s,
};
_settings.FolderLinks.Add(newFolder);
}
lbxFolders.Items.Refresh();
}
}
}
private void LbxFolders_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effects = DragDropEffects.Link;
}
else
{
e.Effects = DragDropEffects.None;
}
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Forms;
using Wox.Plugin;
using DataFormats = System.Windows.DataFormats;
using DragDropEffects = System.Windows.DragDropEffects;
using DragEventArgs = System.Windows.DragEventArgs;
using MessageBox = System.Windows.MessageBox;
namespace Microsoft.Plugin.Folder
{
public partial class FileSystemSettings
{
private IPublicAPI _woxAPI;
private FolderSettings _settings;
public FileSystemSettings(IPublicAPI woxAPI, FolderSettings settings)
{
_woxAPI = woxAPI;
InitializeComponent();
_settings = settings ?? throw new ArgumentNullException(paramName: nameof(settings));
lbxFolders.ItemsSource = _settings.FolderLinks;
}
private void BtnDelete_Click(object sender, RoutedEventArgs e)
{
if (lbxFolders.SelectedItem is FolderLink selectedFolder)
{
string msg = string.Format(CultureInfo.InvariantCulture, _woxAPI.GetTranslation("wox_plugin_folder_delete_folder_link"), selectedFolder.Path);
if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
_settings.FolderLinks.Remove(selectedFolder);
lbxFolders.Items.Refresh();
}
}
else
{
string warning = _woxAPI.GetTranslation("wox_plugin_folder_select_folder_link_warning");
MessageBox.Show(warning);
}
}
private void BtnEdit_Click(object sender, RoutedEventArgs e)
{
if (lbxFolders.SelectedItem is FolderLink selectedFolder)
{
using (var folderBrowserDialog = new FolderBrowserDialog())
{
folderBrowserDialog.SelectedPath = selectedFolder.Path;
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
{
var link = _settings.FolderLinks.First(x => x.Path == selectedFolder.Path);
link.Path = folderBrowserDialog.SelectedPath;
}
lbxFolders.Items.Refresh();
}
}
else
{
string warning = _woxAPI.GetTranslation("wox_plugin_folder_select_folder_link_warning");
MessageBox.Show(warning);
}
}
private void BtnAdd_Click(object sender, RoutedEventArgs e)
{
using (var folderBrowserDialog = new FolderBrowserDialog())
{
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
{
var newFolder = new FolderLink
{
Path = folderBrowserDialog.SelectedPath,
};
_settings.FolderLinks.Add(newFolder);
}
lbxFolders.Items.Refresh();
}
}
private void LbxFolders_Drop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
if (files != null && files.Any())
{
foreach (string s in files)
{
if (Directory.Exists(s))
{
var newFolder = new FolderLink
{
Path = s,
};
_settings.FolderLinks.Add(newFolder);
}
lbxFolders.Items.Refresh();
}
}
}
private void LbxFolders_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effects = DragDropEffects.Link;
}
else
{
e.Effects = DragDropEffects.None;
}
}
}
}

View File

@@ -1,392 +1,392 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using Microsoft.PowerToys.Settings.UI.Lib;
using Wox.Infrastructure;
using Wox.Infrastructure.Storage;
using Wox.Plugin;
namespace Microsoft.Plugin.Folder
{
public class Main : IPlugin, ISettingProvider, IPluginI18n, ISavable, IContextMenu, IDisposable
{
public const string FolderImagePath = "Images\\folder.dark.png";
public const string FileImagePath = "Images\\file.dark.png";
public const string DeleteFileFolderImagePath = "Images\\delete.dark.png";
public const string CopyImagePath = "Images\\copy.dark.png";
private const string _fileExplorerProgramName = "explorer";
private static readonly PluginJsonStorage<FolderSettings> _storage = new PluginJsonStorage<FolderSettings>();
private static readonly FolderSettings _settings = _storage.Load();
private static List<string> _driverNames;
private static PluginInitContext _context;
private IContextMenu _contextMenuLoader;
private static string warningIconPath;
private bool _disposed = false;
public void Save()
{
_storage.Save();
}
public Control CreateSettingPanel()
{
return new FileSystemSettings(_context.API, _settings);
}
public void Init(PluginInitContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_contextMenuLoader = new ContextMenuLoader(context);
InitialDriverList();
_context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(_context.API.GetCurrentTheme());
}
private static void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
warningIconPath = "Images/Warning.light.png";
}
else
{
warningIconPath = "Images/Warning.dark.png";
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "The parameter is unused")]
private void OnThemeChanged(Theme _, Theme newTheme)
{
UpdateIconPath(newTheme);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
public List<Result> Query(Query query)
{
if (query == null)
{
throw new ArgumentNullException(paramName: nameof(query));
}
var results = GetFolderPluginResults(query);
// todo why was this hack here?
foreach (var result in results)
{
result.Score += 10;
}
return results;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
public static List<Result> GetFolderPluginResults(Query query)
{
var results = GetUserFolderResults(query);
string search = query.Search.ToLower(CultureInfo.InvariantCulture);
if (!IsDriveOrSharedFolder(search))
{
return results;
}
results.AddRange(QueryInternalDirectoryExists(query));
return results;
}
private static bool IsDriveOrSharedFolder(string search)
{
if (search == null)
{
throw new ArgumentNullException(nameof(search));
}
if (search.StartsWith(@"\\", StringComparison.InvariantCulture))
{ // share folder
return true;
}
if (_driverNames != null && _driverNames.Any(search.StartsWith))
{ // normal drive letter
return true;
}
if (_driverNames == null && search.Length > 2 && char.IsLetter(search[0]) && search[1] == ':')
{ // when we don't have the drive letters we can try...
return true; // we don't know so let's give it the possibility
}
return false;
}
private static Result CreateFolderResult(string title, string subtitle, string path, Query query)
{
return new Result
{
Title = title,
IcoPath = path,
SubTitle = "Folder: " + subtitle,
QueryTextDisplay = path,
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData,
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path },
Action = c =>
{
Process.Start(_fileExplorerProgramName, path);
return true;
},
};
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
private static List<Result> GetUserFolderResults(Query query)
{
if (query == null)
{
throw new ArgumentNullException(paramName: nameof(query));
}
string search = query.Search.ToLower(CultureInfo.InvariantCulture);
var userFolderLinks = _settings.FolderLinks.Where(
x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
var results = userFolderLinks.Select(item =>
CreateFolderResult(item.Nickname, item.Path, item.Path, query)).ToList();
return results;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
private static void InitialDriverList()
{
if (_driverNames == null)
{
_driverNames = new List<string>();
var allDrives = DriveInfo.GetDrives();
foreach (DriveInfo driver in allDrives)
{
_driverNames.Add(driver.Name.ToLower(CultureInfo.InvariantCulture).TrimEnd('\\'));
}
}
}
private static readonly char[] _specialSearchChars = new char[]
{
'?', '*', '>',
};
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
private static List<Result> QueryInternalDirectoryExists(Query query)
{
var search = query.Search;
var results = new List<Result>();
var hasSpecial = search.IndexOfAny(_specialSearchChars) >= 0;
string incompleteName = string.Empty;
if (hasSpecial || !Directory.Exists(search + "\\"))
{
// if folder doesn't exist, we want to take the last part and use it afterwards to help the user
// find the right folder.
int index = search.LastIndexOf('\\');
if (index > 0 && index < (search.Length - 1))
{
incompleteName = search.Substring(index + 1).ToLower(CultureInfo.InvariantCulture);
search = search.Substring(0, index + 1);
if (!Directory.Exists(search))
{
return results;
}
}
else
{
return results;
}
}
else
{
// folder exist, add \ at the end of doesn't exist
if (!search.EndsWith("\\", StringComparison.InvariantCulture))
{
search += "\\";
}
}
results.Add(CreateOpenCurrentFolderResult(search));
var searchOption = SearchOption.TopDirectoryOnly;
incompleteName += "*";
// give the ability to search all folder when starting with >
if (incompleteName.StartsWith(">", StringComparison.InvariantCulture))
{
searchOption = SearchOption.AllDirectories;
// match everything before and after search term using supported wildcard '*', ie. *searchterm*
incompleteName = "*" + incompleteName.Substring(1);
}
var folderList = new List<Result>();
var fileList = new List<Result>();
try
{
// search folder and add results
var directoryInfo = new DirectoryInfo(search);
var fileSystemInfos = directoryInfo.GetFileSystemInfos(incompleteName, searchOption);
foreach (var fileSystemInfo in fileSystemInfos)
{
if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
{
continue;
}
if (fileSystemInfo is DirectoryInfo)
{
var folderSubtitleString = fileSystemInfo.FullName;
folderList.Add(CreateFolderResult(fileSystemInfo.Name, folderSubtitleString, fileSystemInfo.FullName, query));
}
else
{
fileList.Add(CreateFileResult(fileSystemInfo.FullName, query));
}
}
}
catch (Exception e)
{
if (e is UnauthorizedAccessException || e is ArgumentException)
{
results.Add(new Result { Title = e.Message, Score = 501 });
return results;
}
throw;
}
results = results.Concat(folderList.OrderBy(x => x.Title).Take(_settings.MaxFolderResults)).Concat(fileList.OrderBy(x => x.Title).Take(_settings.MaxFileResults)).ToList();
// Show warning message if result has been truncated
if (folderList.Count > _settings.MaxFolderResults || fileList.Count > _settings.MaxFileResults)
{
var preTruncationCount = folderList.Count + fileList.Count;
var postTruncationCount = Math.Min(folderList.Count, _settings.MaxFolderResults) + Math.Min(fileList.Count, _settings.MaxFileResults);
results.Add(CreateTruncatedItemsResult(search, preTruncationCount, postTruncationCount));
}
return results.ToList();
}
private static Result CreateTruncatedItemsResult(string search, int preTruncationCount, int postTruncationCount)
{
return new Result
{
Title = _context.API.GetTranslation("Microsoft_plugin_folder_truncation_warning_title"),
QueryTextDisplay = search,
SubTitle = string.Format(CultureInfo.InvariantCulture, _context.API.GetTranslation("Microsoft_plugin_folder_truncation_warning_subtitle"), postTruncationCount, preTruncationCount),
IcoPath = warningIconPath,
};
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alve and instead inform the user of the error")]
private static Result CreateFileResult(string filePath, Query query)
{
var result = new Result
{
Title = Path.GetFileName(filePath),
SubTitle = "Folder: " + filePath,
IcoPath = filePath,
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, Path.GetFileName(filePath)).MatchData,
Action = c =>
{
try
{
Process.Start(_fileExplorerProgramName, filePath);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Could not start " + filePath);
}
return true;
},
ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath },
};
return result;
}
private static Result CreateOpenCurrentFolderResult(string search)
{
var firstResult = "Open " + search;
var folderName = search.TrimEnd('\\').Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last();
var sanitizedPath = Regex.Replace(search, @"[\/\\]+", "\\");
// A network path must start with \\
if (sanitizedPath.StartsWith("\\", StringComparison.InvariantCulture))
{
sanitizedPath = sanitizedPath.Insert(0, "\\");
}
return new Result
{
Title = firstResult,
QueryTextDisplay = search,
SubTitle = $"Folder: Use > to search within the directory. Use * to search for file extensions. Or use both >*.",
IcoPath = search,
Score = 500,
Action = c =>
{
Process.Start(_fileExplorerProgramName, sanitizedPath);
return true;
},
};
}
public string GetTranslatedPluginTitle()
{
return _context.API.GetTranslation("wox_plugin_folder_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return _context.API.GetTranslation("wox_plugin_folder_plugin_description");
}
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
return _contextMenuLoader.LoadContextMenus(selectedResult);
}
public void UpdateSettings(PowerLauncherSettings settings)
{
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_context.API.ThemeChanged -= OnThemeChanged;
_disposed = true;
}
}
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using Microsoft.PowerToys.Settings.UI.Lib;
using Wox.Infrastructure;
using Wox.Infrastructure.Storage;
using Wox.Plugin;
namespace Microsoft.Plugin.Folder
{
public class Main : IPlugin, ISettingProvider, IPluginI18n, ISavable, IContextMenu, IDisposable
{
public const string FolderImagePath = "Images\\folder.dark.png";
public const string FileImagePath = "Images\\file.dark.png";
public const string DeleteFileFolderImagePath = "Images\\delete.dark.png";
public const string CopyImagePath = "Images\\copy.dark.png";
private const string _fileExplorerProgramName = "explorer";
private static readonly PluginJsonStorage<FolderSettings> _storage = new PluginJsonStorage<FolderSettings>();
private static readonly FolderSettings _settings = _storage.Load();
private static List<string> _driverNames;
private static PluginInitContext _context;
private IContextMenu _contextMenuLoader;
private static string warningIconPath;
private bool _disposed = false;
public void Save()
{
_storage.Save();
}
public Control CreateSettingPanel()
{
return new FileSystemSettings(_context.API, _settings);
}
public void Init(PluginInitContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_contextMenuLoader = new ContextMenuLoader(context);
InitialDriverList();
_context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(_context.API.GetCurrentTheme());
}
private static void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
warningIconPath = "Images/Warning.light.png";
}
else
{
warningIconPath = "Images/Warning.dark.png";
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1313:Parameter names should begin with lower-case letter", Justification = "The parameter is unused")]
private void OnThemeChanged(Theme _, Theme newTheme)
{
UpdateIconPath(newTheme);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
public List<Result> Query(Query query)
{
if (query == null)
{
throw new ArgumentNullException(paramName: nameof(query));
}
var results = GetFolderPluginResults(query);
// todo why was this hack here?
foreach (var result in results)
{
result.Score += 10;
}
return results;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
public static List<Result> GetFolderPluginResults(Query query)
{
var results = GetUserFolderResults(query);
string search = query.Search.ToLower(CultureInfo.InvariantCulture);
if (!IsDriveOrSharedFolder(search))
{
return results;
}
results.AddRange(QueryInternalDirectoryExists(query));
return results;
}
private static bool IsDriveOrSharedFolder(string search)
{
if (search == null)
{
throw new ArgumentNullException(nameof(search));
}
if (search.StartsWith(@"\\", StringComparison.InvariantCulture))
{ // share folder
return true;
}
if (_driverNames != null && _driverNames.Any(search.StartsWith))
{ // normal drive letter
return true;
}
if (_driverNames == null && search.Length > 2 && char.IsLetter(search[0]) && search[1] == ':')
{ // when we don't have the drive letters we can try...
return true; // we don't know so let's give it the possibility
}
return false;
}
private static Result CreateFolderResult(string title, string subtitle, string path, Query query)
{
return new Result
{
Title = title,
IcoPath = path,
SubTitle = "Folder: " + subtitle,
QueryTextDisplay = path,
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData,
ContextData = new SearchResult { Type = ResultType.Folder, FullPath = path },
Action = c =>
{
Process.Start(_fileExplorerProgramName, path);
return true;
},
};
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
private static List<Result> GetUserFolderResults(Query query)
{
if (query == null)
{
throw new ArgumentNullException(paramName: nameof(query));
}
string search = query.Search.ToLower(CultureInfo.InvariantCulture);
var userFolderLinks = _settings.FolderLinks.Where(
x => x.Nickname.StartsWith(search, StringComparison.OrdinalIgnoreCase));
var results = userFolderLinks.Select(item =>
CreateFolderResult(item.Nickname, item.Path, item.Path, query)).ToList();
return results;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
private static void InitialDriverList()
{
if (_driverNames == null)
{
_driverNames = new List<string>();
var allDrives = DriveInfo.GetDrives();
foreach (DriveInfo driver in allDrives)
{
_driverNames.Add(driver.Name.ToLower(CultureInfo.InvariantCulture).TrimEnd('\\'));
}
}
}
private static readonly char[] _specialSearchChars = new char[]
{
'?', '*', '>',
};
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1308:Normalize strings to uppercase", Justification = "Do not want to change the behavior of the application, but want to enforce static analysis")]
private static List<Result> QueryInternalDirectoryExists(Query query)
{
var search = query.Search;
var results = new List<Result>();
var hasSpecial = search.IndexOfAny(_specialSearchChars) >= 0;
string incompleteName = string.Empty;
if (hasSpecial || !Directory.Exists(search + "\\"))
{
// if folder doesn't exist, we want to take the last part and use it afterwards to help the user
// find the right folder.
int index = search.LastIndexOf('\\');
if (index > 0 && index < (search.Length - 1))
{
incompleteName = search.Substring(index + 1).ToLower(CultureInfo.InvariantCulture);
search = search.Substring(0, index + 1);
if (!Directory.Exists(search))
{
return results;
}
}
else
{
return results;
}
}
else
{
// folder exist, add \ at the end of doesn't exist
if (!search.EndsWith("\\", StringComparison.InvariantCulture))
{
search += "\\";
}
}
results.Add(CreateOpenCurrentFolderResult(search));
var searchOption = SearchOption.TopDirectoryOnly;
incompleteName += "*";
// give the ability to search all folder when starting with >
if (incompleteName.StartsWith(">", StringComparison.InvariantCulture))
{
searchOption = SearchOption.AllDirectories;
// match everything before and after search term using supported wildcard '*', ie. *searchterm*
incompleteName = "*" + incompleteName.Substring(1);
}
var folderList = new List<Result>();
var fileList = new List<Result>();
try
{
// search folder and add results
var directoryInfo = new DirectoryInfo(search);
var fileSystemInfos = directoryInfo.GetFileSystemInfos(incompleteName, searchOption);
foreach (var fileSystemInfo in fileSystemInfos)
{
if ((fileSystemInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
{
continue;
}
if (fileSystemInfo is DirectoryInfo)
{
var folderSubtitleString = fileSystemInfo.FullName;
folderList.Add(CreateFolderResult(fileSystemInfo.Name, folderSubtitleString, fileSystemInfo.FullName, query));
}
else
{
fileList.Add(CreateFileResult(fileSystemInfo.FullName, query));
}
}
}
catch (Exception e)
{
if (e is UnauthorizedAccessException || e is ArgumentException)
{
results.Add(new Result { Title = e.Message, Score = 501 });
return results;
}
throw;
}
results = results.Concat(folderList.OrderBy(x => x.Title).Take(_settings.MaxFolderResults)).Concat(fileList.OrderBy(x => x.Title).Take(_settings.MaxFileResults)).ToList();
// Show warning message if result has been truncated
if (folderList.Count > _settings.MaxFolderResults || fileList.Count > _settings.MaxFileResults)
{
var preTruncationCount = folderList.Count + fileList.Count;
var postTruncationCount = Math.Min(folderList.Count, _settings.MaxFolderResults) + Math.Min(fileList.Count, _settings.MaxFileResults);
results.Add(CreateTruncatedItemsResult(search, preTruncationCount, postTruncationCount));
}
return results.ToList();
}
private static Result CreateTruncatedItemsResult(string search, int preTruncationCount, int postTruncationCount)
{
return new Result
{
Title = _context.API.GetTranslation("Microsoft_plugin_folder_truncation_warning_title"),
QueryTextDisplay = search,
SubTitle = string.Format(CultureInfo.InvariantCulture, _context.API.GetTranslation("Microsoft_plugin_folder_truncation_warning_subtitle"), postTruncationCount, preTruncationCount),
IcoPath = warningIconPath,
};
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alve and instead inform the user of the error")]
private static Result CreateFileResult(string filePath, Query query)
{
var result = new Result
{
Title = Path.GetFileName(filePath),
SubTitle = "Folder: " + filePath,
IcoPath = filePath,
TitleHighlightData = StringMatcher.FuzzySearch(query.Search, Path.GetFileName(filePath)).MatchData,
Action = c =>
{
try
{
Process.Start(_fileExplorerProgramName, filePath);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Could not start " + filePath);
}
return true;
},
ContextData = new SearchResult { Type = ResultType.File, FullPath = filePath },
};
return result;
}
private static Result CreateOpenCurrentFolderResult(string search)
{
var firstResult = "Open " + search;
var folderName = search.TrimEnd('\\').Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None).Last();
var sanitizedPath = Regex.Replace(search, @"[\/\\]+", "\\");
// A network path must start with \\
if (sanitizedPath.StartsWith("\\", StringComparison.InvariantCulture))
{
sanitizedPath = sanitizedPath.Insert(0, "\\");
}
return new Result
{
Title = firstResult,
QueryTextDisplay = search,
SubTitle = $"Folder: Use > to search within the directory. Use * to search for file extensions. Or use both >*.",
IcoPath = search,
Score = 500,
Action = c =>
{
Process.Start(_fileExplorerProgramName, sanitizedPath);
return true;
},
};
}
public string GetTranslatedPluginTitle()
{
return _context.API.GetTranslation("wox_plugin_folder_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return _context.API.GetTranslation("wox_plugin_folder_plugin_description");
}
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
return _contextMenuLoader.LoadContextMenus(selectedResult);
}
public void UpdateSettings(PowerLauncherSettings settings)
{
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_context.API.ThemeChanged -= OnThemeChanged;
_disposed = true;
}
}
}
}
}

View File

@@ -1,196 +1,196 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Microsoft.Plugin.Indexer.SearchHelper;
using Wox.Infrastructure;
using Wox.Infrastructure.Logger;
using Wox.Plugin;
namespace Microsoft.Plugin.Indexer
{
internal class ContextMenuLoader : IContextMenu
{
private readonly PluginInitContext _context;
public enum ResultType
{
Folder,
File,
}
// Extensions for adding run as admin context menu item for applications
private readonly string[] appExtensions = { ".exe", ".bat", ".appref-ms", ".lnk" };
public ContextMenuLoader(PluginInitContext context)
{
_context = context;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log and show an error message")]
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
var contextMenus = new List<ContextMenuResult>();
if (selectedResult.ContextData is SearchResult record)
{
ResultType type = Path.HasExtension(record.Path) ? ResultType.File : ResultType.Folder;
if (type == ResultType.File)
{
contextMenus.Add(CreateOpenContainingFolderResult(record));
}
// Test to check if File can be Run as admin, if yes, we add a 'run as admin' context menu item
if (CanFileBeRunAsAdmin(record.Path))
{
contextMenus.Add(CreateRunAsAdminContextMenu(record));
}
contextMenus.Add(new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_copy_path"),
Glyph = "\xE8C8",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.C,
AcceleratorModifiers = ModifierKeys.Control,
Action = (context) =>
{
try
{
Clipboard.SetText(record.Path);
return true;
}
catch (Exception e)
{
var message = "Fail to set text in clipboard";
LogException(message, e);
_context.API.ShowMsg(message);
return false;
}
},
});
contextMenus.Add(new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_open_in_console"),
Glyph = "\xE756",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.C,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = (context) =>
{
try
{
if (type == ResultType.File)
{
Helper.OpenInConsole(Path.GetDirectoryName(record.Path));
}
else
{
Helper.OpenInConsole(record.Path);
}
return true;
}
catch (Exception e)
{
Log.Exception($"|Microsoft.Plugin.Indexer.ContextMenuLoader.LoadContextMenus| Failed to open {record.Path} in console, {e.Message}", e);
return false;
}
},
});
}
return contextMenus;
}
// Function to add the context menu item to run as admin
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log the exeption message")]
private ContextMenuResult CreateRunAsAdminContextMenu(SearchResult record)
{
return new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_run_as_administrator"),
Glyph = "\xE7EF",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.Enter,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = _ =>
{
try
{
Task.Run(() => Helper.RunAsAdmin(record.Path));
return true;
}
catch (Exception e)
{
Log.Exception($"|Microsoft.Plugin.Indexer.ContextMenu| Failed to run {record.Path} as admin, {e.Message}", e);
return false;
}
},
};
}
// Function to test if the file can be run as admin
private bool CanFileBeRunAsAdmin(string path)
{
string fileExtension = Path.GetExtension(path);
foreach (string extension in appExtensions)
{
if (extension.Equals(fileExtension, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log and show an error message")]
private ContextMenuResult CreateOpenContainingFolderResult(SearchResult record)
{
return new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_open_containing_folder"),
Glyph = "\xE838",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.E,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = _ =>
{
try
{
Process.Start("explorer.exe", $" /select,\"{record.Path}\"");
}
catch (Exception e)
{
var message = $"Fail to open file at {record.Path}";
LogException(message, e);
_context.API.ShowMsg(message);
return false;
}
return true;
},
};
}
public static void LogException(string message, Exception e)
{
Log.Exception($"|Microsoft.Plugin.Folder.ContextMenu|{message}", e);
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using Microsoft.Plugin.Indexer.SearchHelper;
using Wox.Infrastructure;
using Wox.Infrastructure.Logger;
using Wox.Plugin;
namespace Microsoft.Plugin.Indexer
{
internal class ContextMenuLoader : IContextMenu
{
private readonly PluginInitContext _context;
public enum ResultType
{
Folder,
File,
}
// Extensions for adding run as admin context menu item for applications
private readonly string[] appExtensions = { ".exe", ".bat", ".appref-ms", ".lnk" };
public ContextMenuLoader(PluginInitContext context)
{
_context = context;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log and show an error message")]
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
var contextMenus = new List<ContextMenuResult>();
if (selectedResult.ContextData is SearchResult record)
{
ResultType type = Path.HasExtension(record.Path) ? ResultType.File : ResultType.Folder;
if (type == ResultType.File)
{
contextMenus.Add(CreateOpenContainingFolderResult(record));
}
// Test to check if File can be Run as admin, if yes, we add a 'run as admin' context menu item
if (CanFileBeRunAsAdmin(record.Path))
{
contextMenus.Add(CreateRunAsAdminContextMenu(record));
}
contextMenus.Add(new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_copy_path"),
Glyph = "\xE8C8",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.C,
AcceleratorModifiers = ModifierKeys.Control,
Action = (context) =>
{
try
{
Clipboard.SetText(record.Path);
return true;
}
catch (Exception e)
{
var message = "Fail to set text in clipboard";
LogException(message, e);
_context.API.ShowMsg(message);
return false;
}
},
});
contextMenus.Add(new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_open_in_console"),
Glyph = "\xE756",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.C,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = (context) =>
{
try
{
if (type == ResultType.File)
{
Helper.OpenInConsole(Path.GetDirectoryName(record.Path));
}
else
{
Helper.OpenInConsole(record.Path);
}
return true;
}
catch (Exception e)
{
Log.Exception($"|Microsoft.Plugin.Indexer.ContextMenuLoader.LoadContextMenus| Failed to open {record.Path} in console, {e.Message}", e);
return false;
}
},
});
}
return contextMenus;
}
// Function to add the context menu item to run as admin
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log the exeption message")]
private ContextMenuResult CreateRunAsAdminContextMenu(SearchResult record)
{
return new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_run_as_administrator"),
Glyph = "\xE7EF",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.Enter,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = _ =>
{
try
{
Task.Run(() => Helper.RunAsAdmin(record.Path));
return true;
}
catch (Exception e)
{
Log.Exception($"|Microsoft.Plugin.Indexer.ContextMenu| Failed to run {record.Path} as admin, {e.Message}", e);
return false;
}
},
};
}
// Function to test if the file can be run as admin
private bool CanFileBeRunAsAdmin(string path)
{
string fileExtension = Path.GetExtension(path);
foreach (string extension in appExtensions)
{
if (extension.Equals(fileExtension, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive, and instead log and show an error message")]
private ContextMenuResult CreateOpenContainingFolderResult(SearchResult record)
{
return new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_open_containing_folder"),
Glyph = "\xE838",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.E,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = _ =>
{
try
{
Process.Start("explorer.exe", $" /select,\"{record.Path}\"");
}
catch (Exception e)
{
var message = $"Fail to open file at {record.Path}";
LogException(message, e);
_context.API.ShowMsg(message);
return false;
}
return true;
},
};
}
public static void LogException(string message, Exception e)
{
Log.Exception($"|Microsoft.Plugin.Folder.ContextMenu|{message}", e);
}
}
}

View File

@@ -1,267 +1,267 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using Microsoft.Plugin.Indexer.DriveDetection;
using Microsoft.Plugin.Indexer.SearchHelper;
using Microsoft.PowerToys.Settings.UI.Lib;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.Storage;
using Wox.Plugin;
namespace Microsoft.Plugin.Indexer
{
internal class Main : ISettingProvider, IPlugin, ISavable, IPluginI18n, IContextMenu, IDisposable, IDelayedExecutionPlugin
{
// This variable contains metadata about the Plugin
private PluginInitContext _context;
// This variable contains information about the context menus
private IndexerSettings _settings;
// Contains information about the plugin stored in json format
private PluginJsonStorage<IndexerSettings> _storage;
// To access Windows Search functionalities
private static readonly OleDBSearch _search = new OleDBSearch();
private readonly WindowsSearchAPI _api = new WindowsSearchAPI(_search);
// To obtain information regarding the drives that are indexed
private readonly IndexerDriveDetection _driveDetection = new IndexerDriveDetection(new RegistryWrapper());
// Reserved keywords in oleDB
private readonly string reservedStringPattern = @"^[\/\\\$\%]+$";
private string WarningIconPath { get; set; }
private IContextMenu _contextMenuLoader;
private bool disposedValue;
// To save the configurations of plugins
public void Save()
{
_storage.Save();
}
// This function uses the Windows indexer and returns the list of results obtained
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive but will log the exception")]
public List<Result> Query(Query query, bool isFullQuery)
{
var results = new List<Result>();
if (!string.IsNullOrEmpty(query.Search))
{
var searchQuery = query.Search;
if (_settings.MaxSearchCount <= 0)
{
_settings.MaxSearchCount = 30;
}
var regexMatch = Regex.Match(searchQuery, reservedStringPattern);
if (!regexMatch.Success)
{
try
{
if (_driveDetection.DisplayWarning())
{
results.Add(new Result
{
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_drivedetectionwarning"),
SubTitle = _context.API.GetTranslation("Microsoft_plugin_indexer_disable_warning_in_settings"),
IcoPath = WarningIconPath,
Action = e =>
{
try
{
Process.Start(GetWindowsSearchSettingsProcessInfo());
}
catch (Exception ex)
{
Log.Exception("Microsoft.Plugin.Indexer", $"Unable to launch Windows Search Settings: {ex.Message}", ex, "Query");
}
return true;
},
});
}
var searchResultsList = _api.Search(searchQuery, isFullQuery, maxCount: _settings.MaxSearchCount).ToList();
// If the delayed execution query is not required (since the SQL query is fast) return empty results
if (searchResultsList.Count == 0 && isFullQuery)
{
return new List<Result>();
}
foreach (var searchResult in searchResultsList)
{
var path = searchResult.Path;
var toolTipTitle = string.Format(CultureInfo.CurrentCulture, "{0} : {1}", _context.API.GetTranslation("Microsoft_plugin_indexer_name"), searchResult.Title);
var toolTipText = string.Format(CultureInfo.CurrentCulture, "{0} : {1}", _context.API.GetTranslation("Microsoft_plugin_indexer_path"), path);
string workingDir = null;
if (_settings.UseLocationAsWorkingDir)
{
workingDir = Path.GetDirectoryName(path);
}
Result r = new Result();
r.Title = searchResult.Title;
r.SubTitle = "Search: " + path;
r.IcoPath = path;
r.ToolTipData = new ToolTipData(toolTipTitle, toolTipText);
r.Action = c =>
{
bool hide;
try
{
Process.Start(new ProcessStartInfo
{
FileName = path,
UseShellExecute = true,
WorkingDirectory = workingDir,
});
hide = true;
}
catch (Win32Exception)
{
var name = $"Plugin: {_context.CurrentPluginMetadata.Name}";
var msg = "Can't Open this file";
_context.API.ShowMsg(name, msg, string.Empty);
hide = false;
}
return hide;
};
r.ContextData = searchResult;
// If the result is a directory, then it's display should show a directory.
if (Directory.Exists(path))
{
r.QueryTextDisplay = path;
}
results.Add(r);
}
}
catch (InvalidOperationException)
{
// The connection has closed, internal error of ExecuteReader()
// Not showing this exception to the users
}
catch (Exception ex)
{
Log.Info(ex.ToString());
}
}
}
return results;
}
// This function uses the Windows indexer and returns the list of results obtained. This version is required to implement the interface
public List<Result> Query(Query query)
{
return Query(query, false);
}
public void Init(PluginInitContext context)
{
// initialize the context of the plugin
_context = context;
_contextMenuLoader = new ContextMenuLoader(context);
_storage = new PluginJsonStorage<IndexerSettings>();
_settings = _storage.Load();
_context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(_context.API.GetCurrentTheme());
}
// Todo : Update with theme based IconPath
private void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
WarningIconPath = "Images/Warning.light.png";
}
else
{
WarningIconPath = "Images/Warning.dark.png";
}
}
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
{
UpdateIconPath(newTheme);
}
// TODO: Localize the strings
// Set the Plugin Title
public string GetTranslatedPluginTitle()
{
return "Windows Indexer Plugin";
}
// TODO: Localize the string
// Set the plugin Description
public string GetTranslatedPluginDescription()
{
return "Returns files and folders";
}
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
return _contextMenuLoader.LoadContextMenus(selectedResult);
}
public void UpdateSettings(PowerLauncherSettings settings)
{
_driveDetection.IsDriveDetectionWarningCheckBoxSelected = settings.Properties.DisableDriveDetectionWarning;
}
public Control CreateSettingPanel()
{
throw new NotImplementedException();
}
// Returns the Process Start Information for the new Windows Search Settings
public static ProcessStartInfo GetWindowsSearchSettingsProcessInfo()
{
var ps = new ProcessStartInfo("ms-settings:cortana-windowssearch")
{
UseShellExecute = true,
Verb = "open",
};
return ps;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_search.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using Microsoft.Plugin.Indexer.DriveDetection;
using Microsoft.Plugin.Indexer.SearchHelper;
using Microsoft.PowerToys.Settings.UI.Lib;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.Storage;
using Wox.Plugin;
namespace Microsoft.Plugin.Indexer
{
internal class Main : ISettingProvider, IPlugin, ISavable, IPluginI18n, IContextMenu, IDisposable, IDelayedExecutionPlugin
{
// This variable contains metadata about the Plugin
private PluginInitContext _context;
// This variable contains information about the context menus
private IndexerSettings _settings;
// Contains information about the plugin stored in json format
private PluginJsonStorage<IndexerSettings> _storage;
// To access Windows Search functionalities
private static readonly OleDBSearch _search = new OleDBSearch();
private readonly WindowsSearchAPI _api = new WindowsSearchAPI(_search);
// To obtain information regarding the drives that are indexed
private readonly IndexerDriveDetection _driveDetection = new IndexerDriveDetection(new RegistryWrapper());
// Reserved keywords in oleDB
private readonly string reservedStringPattern = @"^[\/\\\$\%]+$";
private string WarningIconPath { get; set; }
private IContextMenu _contextMenuLoader;
private bool disposedValue;
// To save the configurations of plugins
public void Save()
{
_storage.Save();
}
// This function uses the Windows indexer and returns the list of results obtained
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive but will log the exception")]
public List<Result> Query(Query query, bool isFullQuery)
{
var results = new List<Result>();
if (!string.IsNullOrEmpty(query.Search))
{
var searchQuery = query.Search;
if (_settings.MaxSearchCount <= 0)
{
_settings.MaxSearchCount = 30;
}
var regexMatch = Regex.Match(searchQuery, reservedStringPattern);
if (!regexMatch.Success)
{
try
{
if (_driveDetection.DisplayWarning())
{
results.Add(new Result
{
Title = _context.API.GetTranslation("Microsoft_plugin_indexer_drivedetectionwarning"),
SubTitle = _context.API.GetTranslation("Microsoft_plugin_indexer_disable_warning_in_settings"),
IcoPath = WarningIconPath,
Action = e =>
{
try
{
Process.Start(GetWindowsSearchSettingsProcessInfo());
}
catch (Exception ex)
{
Log.Exception("Microsoft.Plugin.Indexer", $"Unable to launch Windows Search Settings: {ex.Message}", ex, "Query");
}
return true;
},
});
}
var searchResultsList = _api.Search(searchQuery, isFullQuery, maxCount: _settings.MaxSearchCount).ToList();
// If the delayed execution query is not required (since the SQL query is fast) return empty results
if (searchResultsList.Count == 0 && isFullQuery)
{
return new List<Result>();
}
foreach (var searchResult in searchResultsList)
{
var path = searchResult.Path;
var toolTipTitle = string.Format(CultureInfo.CurrentCulture, "{0} : {1}", _context.API.GetTranslation("Microsoft_plugin_indexer_name"), searchResult.Title);
var toolTipText = string.Format(CultureInfo.CurrentCulture, "{0} : {1}", _context.API.GetTranslation("Microsoft_plugin_indexer_path"), path);
string workingDir = null;
if (_settings.UseLocationAsWorkingDir)
{
workingDir = Path.GetDirectoryName(path);
}
Result r = new Result();
r.Title = searchResult.Title;
r.SubTitle = "Search: " + path;
r.IcoPath = path;
r.ToolTipData = new ToolTipData(toolTipTitle, toolTipText);
r.Action = c =>
{
bool hide;
try
{
Process.Start(new ProcessStartInfo
{
FileName = path,
UseShellExecute = true,
WorkingDirectory = workingDir,
});
hide = true;
}
catch (Win32Exception)
{
var name = $"Plugin: {_context.CurrentPluginMetadata.Name}";
var msg = "Can't Open this file";
_context.API.ShowMsg(name, msg, string.Empty);
hide = false;
}
return hide;
};
r.ContextData = searchResult;
// If the result is a directory, then it's display should show a directory.
if (Directory.Exists(path))
{
r.QueryTextDisplay = path;
}
results.Add(r);
}
}
catch (InvalidOperationException)
{
// The connection has closed, internal error of ExecuteReader()
// Not showing this exception to the users
}
catch (Exception ex)
{
Log.Info(ex.ToString());
}
}
}
return results;
}
// This function uses the Windows indexer and returns the list of results obtained. This version is required to implement the interface
public List<Result> Query(Query query)
{
return Query(query, false);
}
public void Init(PluginInitContext context)
{
// initialize the context of the plugin
_context = context;
_contextMenuLoader = new ContextMenuLoader(context);
_storage = new PluginJsonStorage<IndexerSettings>();
_settings = _storage.Load();
_context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(_context.API.GetCurrentTheme());
}
// Todo : Update with theme based IconPath
private void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
WarningIconPath = "Images/Warning.light.png";
}
else
{
WarningIconPath = "Images/Warning.dark.png";
}
}
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
{
UpdateIconPath(newTheme);
}
// TODO: Localize the strings
// Set the Plugin Title
public string GetTranslatedPluginTitle()
{
return "Windows Indexer Plugin";
}
// TODO: Localize the string
// Set the plugin Description
public string GetTranslatedPluginDescription()
{
return "Returns files and folders";
}
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
return _contextMenuLoader.LoadContextMenus(selectedResult);
}
public void UpdateSettings(PowerLauncherSettings settings)
{
_driveDetection.IsDriveDetectionWarningCheckBoxSelected = settings.Properties.DisableDriveDetectionWarning;
}
public Control CreateSettingPanel()
{
throw new NotImplementedException();
}
// Returns the Process Start Information for the new Windows Search Settings
public static ProcessStartInfo GetWindowsSearchSettingsProcessInfo()
{
var ps = new ProcessStartInfo("ms-settings:cortana-windowssearch")
{
UseShellExecute = true,
Verb = "open",
};
return ps;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
_search.Dispose();
}
disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -1,122 +1,122 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Data.OleDb;
namespace Microsoft.Plugin.Indexer.SearchHelper
{
public class OleDBSearch : ISearch, IDisposable
{
private OleDbCommand command;
private OleDbConnection conn;
private OleDbDataReader wDSResults;
private bool disposedValue;
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Security",
"CA2100:Review SQL queries for security vulnerabilities",
Justification = "sqlQuery does not come from user input but is generated via the ISearchQueryHelper::GenerateSqlFromUserQuery see: https://docs.microsoft.com/en-us/windows/win32/search/-search-3x-wds-qryidx-searchqueryhelper#using-the-generatesqlfromuserquery-method")]
public List<OleDBResult> Query(string connectionString, string sqlQuery)
{
List<OleDBResult> result = new List<OleDBResult>();
using (conn = new OleDbConnection(connectionString))
{
// open the connection
conn.Open();
try
{
// now create an OleDB command object with the query we built above and the connection we just opened.
using (command = new OleDbCommand(sqlQuery, conn))
{
using (wDSResults = command.ExecuteReader())
{
if (!wDSResults.IsClosed && wDSResults.HasRows)
{
while (!wDSResults.IsClosed && wDSResults.Read())
{
List<object> fieldData = new List<object>(wDSResults.FieldCount);
for (int i = 0; i < wDSResults.FieldCount; i++)
{
fieldData.Add(wDSResults.GetValue(i));
}
result.Add(new OleDBResult(fieldData));
}
}
}
}
}
// AccessViolationException can occur if another query is made before the current query completes. Since the old query would be cancelled we can ignore the exception
catch (System.AccessViolationException)
{
// do nothing
}
}
return result;
}
// Checks if all the variables related to database connection have been properly disposed
public bool HaveAllDisposableItemsBeenDisposed()
{
bool commandDisposed = false;
bool connDisposed = false;
bool resultDisposed = false;
try
{
command.ExecuteReader();
}
catch (InvalidOperationException)
{
commandDisposed = true;
}
try
{
wDSResults.Read();
}
catch (InvalidOperationException)
{
resultDisposed = true;
}
if (conn.State == System.Data.ConnectionState.Closed)
{
connDisposed = true;
}
return commandDisposed && resultDisposed && connDisposed;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
command?.Dispose();
conn?.Dispose();
wDSResults?.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Data.OleDb;
namespace Microsoft.Plugin.Indexer.SearchHelper
{
public class OleDBSearch : ISearch, IDisposable
{
private OleDbCommand command;
private OleDbConnection conn;
private OleDbDataReader wDSResults;
private bool disposedValue;
[System.Diagnostics.CodeAnalysis.SuppressMessage(
"Security",
"CA2100:Review SQL queries for security vulnerabilities",
Justification = "sqlQuery does not come from user input but is generated via the ISearchQueryHelper::GenerateSqlFromUserQuery see: https://docs.microsoft.com/en-us/windows/win32/search/-search-3x-wds-qryidx-searchqueryhelper#using-the-generatesqlfromuserquery-method")]
public List<OleDBResult> Query(string connectionString, string sqlQuery)
{
List<OleDBResult> result = new List<OleDBResult>();
using (conn = new OleDbConnection(connectionString))
{
// open the connection
conn.Open();
try
{
// now create an OleDB command object with the query we built above and the connection we just opened.
using (command = new OleDbCommand(sqlQuery, conn))
{
using (wDSResults = command.ExecuteReader())
{
if (!wDSResults.IsClosed && wDSResults.HasRows)
{
while (!wDSResults.IsClosed && wDSResults.Read())
{
List<object> fieldData = new List<object>(wDSResults.FieldCount);
for (int i = 0; i < wDSResults.FieldCount; i++)
{
fieldData.Add(wDSResults.GetValue(i));
}
result.Add(new OleDBResult(fieldData));
}
}
}
}
}
// AccessViolationException can occur if another query is made before the current query completes. Since the old query would be cancelled we can ignore the exception
catch (System.AccessViolationException)
{
// do nothing
}
}
return result;
}
// Checks if all the variables related to database connection have been properly disposed
public bool HaveAllDisposableItemsBeenDisposed()
{
bool commandDisposed = false;
bool connDisposed = false;
bool resultDisposed = false;
try
{
command.ExecuteReader();
}
catch (InvalidOperationException)
{
commandDisposed = true;
}
try
{
wDSResults.Read();
}
catch (InvalidOperationException)
{
resultDisposed = true;
}
if (conn.State == System.Data.ConnectionState.Closed)
{
connDisposed = true;
}
return commandDisposed && resultDisposed && connDisposed;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
command?.Dispose();
conn?.Dispose();
wDSResults?.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -1,150 +1,150 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.Search.Interop;
namespace Microsoft.Plugin.Indexer.SearchHelper
{
public class WindowsSearchAPI
{
public bool DisplayHiddenFiles { get; set; }
private readonly ISearch windowsIndexerSearch;
private const uint _fileAttributeHidden = 0x2;
private static readonly Regex _likeRegex = new Regex(@"[^\s(]+\s+LIKE\s+'([^']|'')*'\s+OR\s+", RegexOptions.Compiled);
public WindowsSearchAPI(ISearch windowsIndexerSearch, bool displayHiddenFiles = false)
{
this.windowsIndexerSearch = windowsIndexerSearch;
DisplayHiddenFiles = displayHiddenFiles;
}
public List<SearchResult> ExecuteQuery(ISearchQueryHelper queryHelper, string keyword, bool isFullQuery = false)
{
if (queryHelper == null)
{
throw new ArgumentNullException(paramName: nameof(queryHelper));
}
List<SearchResult> results = new List<SearchResult>();
// Generate SQL from our parameters, converting the userQuery from AQS->WHERE clause
string sqlQuery = queryHelper.GenerateSQLFromUserQuery(keyword);
var simplifiedQuery = SimplifyQuery(sqlQuery);
if (!isFullQuery)
{
sqlQuery = simplifiedQuery;
}
else if (simplifiedQuery.Equals(sqlQuery, StringComparison.CurrentCultureIgnoreCase))
{
// if a full query is requested but there is no difference between the queries, return empty results
return results;
}
// execute the command, which returns the results as an OleDBResults.
List<OleDBResult> oleDBResults = windowsIndexerSearch.Query(queryHelper.ConnectionString, sqlQuery);
// Loop over all records from the database
foreach (OleDBResult oleDBResult in oleDBResults)
{
if (oleDBResult.FieldData[0] == DBNull.Value || oleDBResult.FieldData[1] == DBNull.Value)
{
continue;
}
var uri_path = new Uri((string)oleDBResult.FieldData[0]);
var result = new SearchResult
{
Path = uri_path.LocalPath,
Title = (string)oleDBResult.FieldData[1],
};
results.Add(result);
}
return results;
}
public static void ModifyQueryHelper(ref ISearchQueryHelper queryHelper, string pattern)
{
if (pattern == null)
{
throw new ArgumentNullException(paramName: nameof(pattern));
}
if (queryHelper == null)
{
throw new ArgumentNullException(paramName: nameof(queryHelper));
}
// convert file pattern if it is not '*'. Don't create restriction for '*' as it includes all files.
if (pattern != "*")
{
pattern = pattern.Replace("*", "%", StringComparison.InvariantCulture);
pattern = pattern.Replace("?", "_", StringComparison.InvariantCulture);
if (pattern.Contains("%", StringComparison.InvariantCulture) || pattern.Contains("_", StringComparison.InvariantCulture))
{
queryHelper.QueryWhereRestrictions += " AND System.FileName LIKE '" + pattern + "' ";
}
else
{
// if there are no wildcards we can use a contains which is much faster as it uses the index
queryHelper.QueryWhereRestrictions += " AND Contains(System.FileName, '" + pattern + "') ";
}
}
}
public static void InitQueryHelper(out ISearchQueryHelper queryHelper, int maxCount, bool displayHiddenFiles)
{
// This uses the Microsoft.Search.Interop assembly
CSearchManager manager = new CSearchManager();
// SystemIndex catalog is the default catalog in Windows
ISearchCatalogManager catalogManager = manager.GetCatalog("SystemIndex");
// Get the ISearchQueryHelper which will help us to translate AQS --> SQL necessary to query the indexer
queryHelper = catalogManager.GetQueryHelper();
// Set the number of results we want. Don't set this property if all results are needed.
queryHelper.QueryMaxResults = maxCount;
// Set list of columns we want to display, getting the path presently
queryHelper.QuerySelectColumns = "System.ItemUrl, System.FileName, System.FileAttributes";
// Set additional query restriction
queryHelper.QueryWhereRestrictions = "AND scope='file:'";
if (!displayHiddenFiles)
{
// https://docs.microsoft.com/en-us/windows/win32/search/all-bitwise
queryHelper.QueryWhereRestrictions += " AND System.FileAttributes <> SOME BITWISE " + _fileAttributeHidden;
}
// To filter based on title for now
queryHelper.QueryContentProperties = "System.FileName";
// Set sorting order
queryHelper.QuerySorting = "System.DateModified DESC";
}
public IEnumerable<SearchResult> Search(string keyword, bool isFullQuery = false, string pattern = "*", int maxCount = 30)
{
ISearchQueryHelper queryHelper;
InitQueryHelper(out queryHelper, maxCount, DisplayHiddenFiles);
ModifyQueryHelper(ref queryHelper, pattern);
return ExecuteQuery(queryHelper, keyword, isFullQuery);
}
public static string SimplifyQuery(string sqlQuery)
{
return _likeRegex.Replace(sqlQuery, string.Empty);
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.Search.Interop;
namespace Microsoft.Plugin.Indexer.SearchHelper
{
public class WindowsSearchAPI
{
public bool DisplayHiddenFiles { get; set; }
private readonly ISearch windowsIndexerSearch;
private const uint _fileAttributeHidden = 0x2;
private static readonly Regex _likeRegex = new Regex(@"[^\s(]+\s+LIKE\s+'([^']|'')*'\s+OR\s+", RegexOptions.Compiled);
public WindowsSearchAPI(ISearch windowsIndexerSearch, bool displayHiddenFiles = false)
{
this.windowsIndexerSearch = windowsIndexerSearch;
DisplayHiddenFiles = displayHiddenFiles;
}
public List<SearchResult> ExecuteQuery(ISearchQueryHelper queryHelper, string keyword, bool isFullQuery = false)
{
if (queryHelper == null)
{
throw new ArgumentNullException(paramName: nameof(queryHelper));
}
List<SearchResult> results = new List<SearchResult>();
// Generate SQL from our parameters, converting the userQuery from AQS->WHERE clause
string sqlQuery = queryHelper.GenerateSQLFromUserQuery(keyword);
var simplifiedQuery = SimplifyQuery(sqlQuery);
if (!isFullQuery)
{
sqlQuery = simplifiedQuery;
}
else if (simplifiedQuery.Equals(sqlQuery, StringComparison.CurrentCultureIgnoreCase))
{
// if a full query is requested but there is no difference between the queries, return empty results
return results;
}
// execute the command, which returns the results as an OleDBResults.
List<OleDBResult> oleDBResults = windowsIndexerSearch.Query(queryHelper.ConnectionString, sqlQuery);
// Loop over all records from the database
foreach (OleDBResult oleDBResult in oleDBResults)
{
if (oleDBResult.FieldData[0] == DBNull.Value || oleDBResult.FieldData[1] == DBNull.Value)
{
continue;
}
var uri_path = new Uri((string)oleDBResult.FieldData[0]);
var result = new SearchResult
{
Path = uri_path.LocalPath,
Title = (string)oleDBResult.FieldData[1],
};
results.Add(result);
}
return results;
}
public static void ModifyQueryHelper(ref ISearchQueryHelper queryHelper, string pattern)
{
if (pattern == null)
{
throw new ArgumentNullException(paramName: nameof(pattern));
}
if (queryHelper == null)
{
throw new ArgumentNullException(paramName: nameof(queryHelper));
}
// convert file pattern if it is not '*'. Don't create restriction for '*' as it includes all files.
if (pattern != "*")
{
pattern = pattern.Replace("*", "%", StringComparison.InvariantCulture);
pattern = pattern.Replace("?", "_", StringComparison.InvariantCulture);
if (pattern.Contains("%", StringComparison.InvariantCulture) || pattern.Contains("_", StringComparison.InvariantCulture))
{
queryHelper.QueryWhereRestrictions += " AND System.FileName LIKE '" + pattern + "' ";
}
else
{
// if there are no wildcards we can use a contains which is much faster as it uses the index
queryHelper.QueryWhereRestrictions += " AND Contains(System.FileName, '" + pattern + "') ";
}
}
}
public static void InitQueryHelper(out ISearchQueryHelper queryHelper, int maxCount, bool displayHiddenFiles)
{
// This uses the Microsoft.Search.Interop assembly
CSearchManager manager = new CSearchManager();
// SystemIndex catalog is the default catalog in Windows
ISearchCatalogManager catalogManager = manager.GetCatalog("SystemIndex");
// Get the ISearchQueryHelper which will help us to translate AQS --> SQL necessary to query the indexer
queryHelper = catalogManager.GetQueryHelper();
// Set the number of results we want. Don't set this property if all results are needed.
queryHelper.QueryMaxResults = maxCount;
// Set list of columns we want to display, getting the path presently
queryHelper.QuerySelectColumns = "System.ItemUrl, System.FileName, System.FileAttributes";
// Set additional query restriction
queryHelper.QueryWhereRestrictions = "AND scope='file:'";
if (!displayHiddenFiles)
{
// https://docs.microsoft.com/en-us/windows/win32/search/all-bitwise
queryHelper.QueryWhereRestrictions += " AND System.FileAttributes <> SOME BITWISE " + _fileAttributeHidden;
}
// To filter based on title for now
queryHelper.QueryContentProperties = "System.FileName";
// Set sorting order
queryHelper.QuerySorting = "System.DateModified DESC";
}
public IEnumerable<SearchResult> Search(string keyword, bool isFullQuery = false, string pattern = "*", int maxCount = 30)
{
ISearchQueryHelper queryHelper;
InitQueryHelper(out queryHelper, maxCount, DisplayHiddenFiles);
ModifyQueryHelper(ref queryHelper, pattern);
return ExecuteQuery(queryHelper, keyword, isFullQuery);
}
public static string SimplifyQuery(string sqlQuery)
{
return _likeRegex.Replace(sqlQuery, string.Empty);
}
}
}

View File

@@ -1,449 +1,449 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using Wox.Infrastructure;
using Wox.Plugin;
namespace Microsoft.Plugin.Program.UnitTests.Programs
{
using Win32Program = Microsoft.Plugin.Program.Programs.Win32Program;
[TestFixture]
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using Wox.Infrastructure;
using Wox.Plugin;
namespace Microsoft.Plugin.Program.UnitTests.Programs
{
using Win32Program = Microsoft.Plugin.Program.Programs.Win32Program;
[TestFixture]
public class Win32Tests
{
private static readonly Win32Program _notepadAppdata = new Win32Program
{
Name = "Notepad",
ExecutableName = "notepad.exe",
FullPath = "c:\\windows\\system32\\notepad.exe",
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\accessories\\notepad.lnk",
AppType = 2,
};
private static readonly Win32Program _notepadUsers = new Win32Program
{
Name = "Notepad",
ExecutableName = "notepad.exe",
FullPath = "c:\\windows\\system32\\notepad.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\accessories\\notepad.lnk",
AppType = 2,
};
private static readonly Win32Program _azureCommandPrompt = new Win32Program
{
Name = "Microsoft Azure Command Prompt - v2.9",
ExecutableName = "cmd.exe",
FullPath = "c:\\windows\\system32\\cmd.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\microsoft azure\\microsoft azure sdk for .net\\v2.9\\microsoft azure command prompt - v2.9.lnk",
AppType = 2,
};
private static readonly Win32Program _visualStudioCommandPrompt = new Win32Program
{
Name = "x64 Native Tools Command Prompt for VS 2019",
ExecutableName = "cmd.exe",
FullPath = "c:\\windows\\system32\\cmd.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\visual studio 2019\\visual studio tools\\vc\\x64 native tools command prompt for vs 2019.lnk",
AppType = 2,
};
private static readonly Win32Program _commandPrompt = new Win32Program
{
Name = "Command Prompt",
ExecutableName = "cmd.exe",
FullPath = "c:\\windows\\system32\\cmd.exe",
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\system tools\\command prompt.lnk",
AppType = 2,
};
private static readonly Win32Program _fileExplorer = new Win32Program
{
Name = "File Explorer",
ExecutableName = "File Explorer.lnk",
FullPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\system tools\\file explorer.lnk",
LnkResolvedPath = null,
AppType = 2,
};
private static readonly Win32Program _wordpad = new Win32Program
{
Name = "Wordpad",
ExecutableName = "wordpad.exe",
FullPath = "c:\\program files\\windows nt\\accessories\\wordpad.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\accessories\\wordpad.lnk",
AppType = 2,
};
private static readonly Win32Program _wordpadDuplicate = new Win32Program
{
Name = "WORDPAD",
ExecutableName = "WORDPAD.EXE",
FullPath = "c:\\program files\\windows nt\\accessories\\wordpad.exe",
LnkResolvedPath = null,
AppType = 2,
};
private static readonly Win32Program _twitterChromePwa = new Win32Program
{
Name = "Twitter",
FullPath = "c:\\program files (x86)\\google\\chrome\\application\\chrome_proxy.exe",
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\chrome apps\\twitter.lnk",
Arguments = " --profile-directory=Default --app-id=jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi",
AppType = 0,
};
private static readonly Win32Program _pinnedWebpage = new Win32Program
{
Name = "Web page",
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe",
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\web page.lnk",
Arguments = "--profile-directory=Default --app-id=homljgmgpmcbpjbnjpfijnhipfkiclkd",
AppType = 0,
};
private static readonly Win32Program _edgeNamedPinnedWebpage = new Win32Program
{
Name = "edge - Bing",
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe",
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\edge - bing.lnk",
Arguments = " --profile-directory=Default --app-id=aocfnapldcnfbofgmbbllojgocaelgdd",
AppType = 0,
};
private static readonly Win32Program _msedge = new Win32Program
{
Name = "Microsoft Edge",
ExecutableName = "msedge.exe",
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\microsoft edge.lnk",
AppType = 2,
};
private static readonly Win32Program _chrome = new Win32Program
{
Name = "Google Chrome",
ExecutableName = "chrome.exe",
FullPath = "c:\\program files (x86)\\google\\chrome\\application\\chrome.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\google chrome.lnk",
AppType = 2,
};
private static readonly Win32Program _dummyProxyApp = new Win32Program
{
Name = "Proxy App",
ExecutableName = "test_proxy.exe",
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\test_proxy.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\test proxy.lnk",
AppType = 2,
};
private static readonly Win32Program _cmdRunCommand = new Win32Program
{
Name = "cmd",
ExecutableName = "cmd.exe",
FullPath = "c:\\windows\\system32\\cmd.exe",
LnkResolvedPath = null,
AppType = 3, // Run command
};
private static readonly Win32Program _cmderRunCommand = new Win32Program
{
Name = "Cmder",
Description = "Cmder: Lovely Console Emulator",
ExecutableName = "Cmder.exe",
FullPath = "c:\\tools\\cmder\\cmder.exe",
LnkResolvedPath = null,
AppType = 3, // Run command
};
private static readonly Win32Program _dummyInternetShortcutApp = new Win32Program
{
Name = "Shop Titans",
ExecutableName = "Shop Titans.url",
FullPath = "steam://rungameid/1258080",
ParentDirectory = "C:\\Users\\temp\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Steam",
LnkResolvedPath = null,
AppType = 1,
};
private static readonly Win32Program _dummyInternetShortcutAppDuplicate = new Win32Program
{
Name = "Shop Titans",
ExecutableName = "Shop Titans.url",
FullPath = "steam://rungameid/1258080",
ParentDirectory = "C:\\Users\\temp\\Desktop",
LnkResolvedPath = null,
AppType = 1,
};
[Test]
public void DedupFunctionWhenCalledMustRemoveDuplicateNotepads()
{
// Arrange
List<Win32Program> prgms = new List<Win32Program>
{
_notepadAppdata,
_notepadUsers,
};
// Act
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
// Assert
Assert.AreEqual(apps.Length, 1);
}
[Test]
public void DedupFunctionWhenCalledMustRemoveInternetShortcuts()
{
// Arrange
List<Win32Program> prgms = new List<Win32Program>
{
_dummyInternetShortcutApp,
_dummyInternetShortcutAppDuplicate,
};
// Act
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
// Assert
Assert.AreEqual(apps.Length, 1);
}
[Test]
public void DedupFunctionWhenCalledMustNotRemovelnkWhichdoesNotHaveExe()
{
// Arrange
List<Win32Program> prgms = new List<Win32Program>
{
_fileExplorer,
};
// Act
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
// Assert
Assert.AreEqual(apps.Length, 1);
}
[Test]
public void DedupFunctionMustRemoveDuplicatesForExeExtensionsWithoutLnkResolvedPath()
{
// Arrange
List<Win32Program> prgms = new List<Win32Program>
{
_wordpad,
_wordpadDuplicate,
};
// Act
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
// Assert
Assert.AreEqual(apps.Length, 1);
Assert.IsTrue(!string.IsNullOrEmpty(apps[0].LnkResolvedPath));
}
[Test]
public void DedupFunctionMustNotRemoveProgramsWithSameExeNameAndFullPath()
{
// Arrange
List<Win32Program> prgms = new List<Win32Program>
{
_azureCommandPrompt,
_visualStudioCommandPrompt,
_commandPrompt,
};
// Act
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
// Assert
Assert.AreEqual(apps.Length, 3);
}
[Test]
public void FunctionIsWebApplicationShouldReturnTrueForWebApplications()
{
// The IsWebApplication(() function must return true for all PWAs and pinned web pages
Assert.IsTrue(_twitterChromePwa.IsWebApplication());
Assert.IsTrue(_pinnedWebpage.IsWebApplication());
Assert.IsTrue(_edgeNamedPinnedWebpage.IsWebApplication());
// Should not filter apps whose executable name ends with proxy.exe
Assert.IsFalse(_dummyProxyApp.IsWebApplication());
}
[TestCase("ignore")]
public void FunctionFilterWebApplicationShouldReturnFalseWhenSearchingForTheMainApp(string query)
{
// Irrespective of the query, the FilterWebApplication() Function must not filter main apps such as edge and chrome
Assert.IsFalse(_msedge.FilterWebApplication(query));
Assert.IsFalse(_chrome.FilterWebApplication(query));
}
[TestCase("edge", ExpectedResult = true)]
[TestCase("EDGE", ExpectedResult = true)]
[TestCase("msedge", ExpectedResult = true)]
[TestCase("Microsoft", ExpectedResult = true)]
[TestCase("edg", ExpectedResult = true)]
[TestCase("Edge page", ExpectedResult = false)]
[TestCase("Edge Web page", ExpectedResult = false)]
public bool EdgeWebSitesShouldBeFilteredWhenSearchingForEdge(string query)
{
return _pinnedWebpage.FilterWebApplication(query);
}
[TestCase("chrome", ExpectedResult = true)]
[TestCase("CHROME", ExpectedResult = true)]
[TestCase("Google", ExpectedResult = true)]
[TestCase("Google Chrome", ExpectedResult = true)]
[TestCase("Google Chrome twitter", ExpectedResult = false)]
public bool ChromeWebSitesShouldBeFilteredWhenSearchingForChrome(string query)
{
return _twitterChromePwa.FilterWebApplication(query);
}
[TestCase("twitter", 0, ExpectedResult = false)]
[TestCase("Twit", 0, ExpectedResult = false)]
[TestCase("TWITTER", 0, ExpectedResult = false)]
[TestCase("web", 1, ExpectedResult = false)]
[TestCase("Page", 1, ExpectedResult = false)]
[TestCase("WEB PAGE", 1, ExpectedResult = false)]
[TestCase("edge", 2, ExpectedResult = false)]
[TestCase("EDGE", 2, ExpectedResult = false)]
public bool PinnedWebPagesShouldNotBeFilteredWhenSearchingForThem(string query, int scenario)
{
const int CASE_TWITTER = 0;
const int CASE_WEB_PAGE = 1;
const int CASE_EDGE_NAMED_WEBPAGE = 2;
// If the query is a part of the name of the web application, it should not be filtered,
// even if the name is the same as that of the main application, eg: case 2 - edge
switch (scenario)
{
case CASE_TWITTER:
return _twitterChromePwa.FilterWebApplication(query);
case CASE_WEB_PAGE:
return _pinnedWebpage.FilterWebApplication(query);
case CASE_EDGE_NAMED_WEBPAGE:
return _edgeNamedPinnedWebpage.FilterWebApplication(query);
default:
break;
}
// unreachable code
return true;
}
[TestCase("Command Prompt")]
[TestCase("cmd")]
[TestCase("cmd.exe")]
[TestCase("ignoreQueryText")]
public void Win32ApplicationsShouldNotBeFilteredWhenFilteringRunCommands(string query)
{
// Even if there is an exact match in the name or exe name, win32 applications should never be filtered
Assert.IsTrue(_commandPrompt.QueryEqualsNameForRunCommands(query));
}
[TestCase("cmd")]
[TestCase("Cmd")]
[TestCase("CMD")]
public void RunCommandsShouldNotBeFilteredOnExactMatch(string query)
{
// Partial matches should be filtered as cmd is not equal to cmder
Assert.IsFalse(_cmderRunCommand.QueryEqualsNameForRunCommands(query));
// the query matches the name (cmd) and is therefore not filtered (case-insensitive)
Assert.IsTrue(_cmdRunCommand.QueryEqualsNameForRunCommands(query));
}
[Test]
public void WebApplicationShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
{
// Arrange
var mock = new Mock<IPublicAPI>();
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
// Act
List<ContextMenuResult> contextMenuResults = _pinnedWebpage.ContextMenus(mock.Object);
// Assert
Assert.AreEqual(contextMenuResults.Count, 3);
mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
}
[Test]
public void InternetShortcutApplicationShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
{
// Arrange
var mock = new Mock<IPublicAPI>();
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
// Act
List<ContextMenuResult> contextMenuResults = _dummyInternetShortcutApp.ContextMenus(mock.Object);
// Assert
Assert.AreEqual(contextMenuResults.Count, 2);
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
}
[Test]
public void Win32ApplicationShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
{
// Arrange
var mock = new Mock<IPublicAPI>();
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
// Act
List<ContextMenuResult> contextMenuResults = _chrome.ContextMenus(mock.Object);
// Assert
Assert.AreEqual(contextMenuResults.Count, 3);
mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
}
[Test]
public void RunCommandShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
{
// Arrange
var mock = new Mock<IPublicAPI>();
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
// Act
List<ContextMenuResult> contextMenuResults = _cmdRunCommand.ContextMenus(mock.Object);
// Assert
Assert.AreEqual(contextMenuResults.Count, 3);
mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
}
[Test]
public void Win32AppsShouldSetNameAsTitleWhileCreatingResult()
{
var mock = new Mock<IPublicAPI>();
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
StringMatcher.Instance = new StringMatcher();
// Act
var result = _cmderRunCommand.Result("cmder", mock.Object);
// Assert
Assert.IsTrue(result.Title.Equals(_cmderRunCommand.Name, StringComparison.Ordinal));
Assert.IsFalse(result.Title.Equals(_cmderRunCommand.Description, StringComparison.Ordinal));
}
}
}
{
private static readonly Win32Program _notepadAppdata = new Win32Program
{
Name = "Notepad",
ExecutableName = "notepad.exe",
FullPath = "c:\\windows\\system32\\notepad.exe",
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\accessories\\notepad.lnk",
AppType = 2,
};
private static readonly Win32Program _notepadUsers = new Win32Program
{
Name = "Notepad",
ExecutableName = "notepad.exe",
FullPath = "c:\\windows\\system32\\notepad.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\accessories\\notepad.lnk",
AppType = 2,
};
private static readonly Win32Program _azureCommandPrompt = new Win32Program
{
Name = "Microsoft Azure Command Prompt - v2.9",
ExecutableName = "cmd.exe",
FullPath = "c:\\windows\\system32\\cmd.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\microsoft azure\\microsoft azure sdk for .net\\v2.9\\microsoft azure command prompt - v2.9.lnk",
AppType = 2,
};
private static readonly Win32Program _visualStudioCommandPrompt = new Win32Program
{
Name = "x64 Native Tools Command Prompt for VS 2019",
ExecutableName = "cmd.exe",
FullPath = "c:\\windows\\system32\\cmd.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\visual studio 2019\\visual studio tools\\vc\\x64 native tools command prompt for vs 2019.lnk",
AppType = 2,
};
private static readonly Win32Program _commandPrompt = new Win32Program
{
Name = "Command Prompt",
ExecutableName = "cmd.exe",
FullPath = "c:\\windows\\system32\\cmd.exe",
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\system tools\\command prompt.lnk",
AppType = 2,
};
private static readonly Win32Program _fileExplorer = new Win32Program
{
Name = "File Explorer",
ExecutableName = "File Explorer.lnk",
FullPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\system tools\\file explorer.lnk",
LnkResolvedPath = null,
AppType = 2,
};
private static readonly Win32Program _wordpad = new Win32Program
{
Name = "Wordpad",
ExecutableName = "wordpad.exe",
FullPath = "c:\\program files\\windows nt\\accessories\\wordpad.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\accessories\\wordpad.lnk",
AppType = 2,
};
private static readonly Win32Program _wordpadDuplicate = new Win32Program
{
Name = "WORDPAD",
ExecutableName = "WORDPAD.EXE",
FullPath = "c:\\program files\\windows nt\\accessories\\wordpad.exe",
LnkResolvedPath = null,
AppType = 2,
};
private static readonly Win32Program _twitterChromePwa = new Win32Program
{
Name = "Twitter",
FullPath = "c:\\program files (x86)\\google\\chrome\\application\\chrome_proxy.exe",
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\chrome apps\\twitter.lnk",
Arguments = " --profile-directory=Default --app-id=jgeosdfsdsgmkedfgdfgdfgbkmhcgcflmi",
AppType = 0,
};
private static readonly Win32Program _pinnedWebpage = new Win32Program
{
Name = "Web page",
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe",
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\web page.lnk",
Arguments = "--profile-directory=Default --app-id=homljgmgpmcbpjbnjpfijnhipfkiclkd",
AppType = 0,
};
private static readonly Win32Program _edgeNamedPinnedWebpage = new Win32Program
{
Name = "edge - Bing",
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe",
LnkResolvedPath = "c:\\users\\powertoys\\appdata\\roaming\\microsoft\\windows\\start menu\\programs\\edge - bing.lnk",
Arguments = " --profile-directory=Default --app-id=aocfnapldcnfbofgmbbllojgocaelgdd",
AppType = 0,
};
private static readonly Win32Program _msedge = new Win32Program
{
Name = "Microsoft Edge",
ExecutableName = "msedge.exe",
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\microsoft edge.lnk",
AppType = 2,
};
private static readonly Win32Program _chrome = new Win32Program
{
Name = "Google Chrome",
ExecutableName = "chrome.exe",
FullPath = "c:\\program files (x86)\\google\\chrome\\application\\chrome.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\google chrome.lnk",
AppType = 2,
};
private static readonly Win32Program _dummyProxyApp = new Win32Program
{
Name = "Proxy App",
ExecutableName = "test_proxy.exe",
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\test_proxy.exe",
LnkResolvedPath = "c:\\programdata\\microsoft\\windows\\start menu\\programs\\test proxy.lnk",
AppType = 2,
};
private static readonly Win32Program _cmdRunCommand = new Win32Program
{
Name = "cmd",
ExecutableName = "cmd.exe",
FullPath = "c:\\windows\\system32\\cmd.exe",
LnkResolvedPath = null,
AppType = 3, // Run command
};
private static readonly Win32Program _cmderRunCommand = new Win32Program
{
Name = "Cmder",
Description = "Cmder: Lovely Console Emulator",
ExecutableName = "Cmder.exe",
FullPath = "c:\\tools\\cmder\\cmder.exe",
LnkResolvedPath = null,
AppType = 3, // Run command
};
private static readonly Win32Program _dummyInternetShortcutApp = new Win32Program
{
Name = "Shop Titans",
ExecutableName = "Shop Titans.url",
FullPath = "steam://rungameid/1258080",
ParentDirectory = "C:\\Users\\temp\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Steam",
LnkResolvedPath = null,
AppType = 1,
};
private static readonly Win32Program _dummyInternetShortcutAppDuplicate = new Win32Program
{
Name = "Shop Titans",
ExecutableName = "Shop Titans.url",
FullPath = "steam://rungameid/1258080",
ParentDirectory = "C:\\Users\\temp\\Desktop",
LnkResolvedPath = null,
AppType = 1,
};
[Test]
public void DedupFunctionWhenCalledMustRemoveDuplicateNotepads()
{
// Arrange
List<Win32Program> prgms = new List<Win32Program>
{
_notepadAppdata,
_notepadUsers,
};
// Act
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
// Assert
Assert.AreEqual(apps.Length, 1);
}
[Test]
public void DedupFunctionWhenCalledMustRemoveInternetShortcuts()
{
// Arrange
List<Win32Program> prgms = new List<Win32Program>
{
_dummyInternetShortcutApp,
_dummyInternetShortcutAppDuplicate,
};
// Act
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
// Assert
Assert.AreEqual(apps.Length, 1);
}
[Test]
public void DedupFunctionWhenCalledMustNotRemovelnkWhichdoesNotHaveExe()
{
// Arrange
List<Win32Program> prgms = new List<Win32Program>
{
_fileExplorer,
};
// Act
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
// Assert
Assert.AreEqual(apps.Length, 1);
}
[Test]
public void DedupFunctionMustRemoveDuplicatesForExeExtensionsWithoutLnkResolvedPath()
{
// Arrange
List<Win32Program> prgms = new List<Win32Program>
{
_wordpad,
_wordpadDuplicate,
};
// Act
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
// Assert
Assert.AreEqual(apps.Length, 1);
Assert.IsTrue(!string.IsNullOrEmpty(apps[0].LnkResolvedPath));
}
[Test]
public void DedupFunctionMustNotRemoveProgramsWithSameExeNameAndFullPath()
{
// Arrange
List<Win32Program> prgms = new List<Win32Program>
{
_azureCommandPrompt,
_visualStudioCommandPrompt,
_commandPrompt,
};
// Act
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
// Assert
Assert.AreEqual(apps.Length, 3);
}
[Test]
public void FunctionIsWebApplicationShouldReturnTrueForWebApplications()
{
// The IsWebApplication(() function must return true for all PWAs and pinned web pages
Assert.IsTrue(_twitterChromePwa.IsWebApplication());
Assert.IsTrue(_pinnedWebpage.IsWebApplication());
Assert.IsTrue(_edgeNamedPinnedWebpage.IsWebApplication());
// Should not filter apps whose executable name ends with proxy.exe
Assert.IsFalse(_dummyProxyApp.IsWebApplication());
}
[TestCase("ignore")]
public void FunctionFilterWebApplicationShouldReturnFalseWhenSearchingForTheMainApp(string query)
{
// Irrespective of the query, the FilterWebApplication() Function must not filter main apps such as edge and chrome
Assert.IsFalse(_msedge.FilterWebApplication(query));
Assert.IsFalse(_chrome.FilterWebApplication(query));
}
[TestCase("edge", ExpectedResult = true)]
[TestCase("EDGE", ExpectedResult = true)]
[TestCase("msedge", ExpectedResult = true)]
[TestCase("Microsoft", ExpectedResult = true)]
[TestCase("edg", ExpectedResult = true)]
[TestCase("Edge page", ExpectedResult = false)]
[TestCase("Edge Web page", ExpectedResult = false)]
public bool EdgeWebSitesShouldBeFilteredWhenSearchingForEdge(string query)
{
return _pinnedWebpage.FilterWebApplication(query);
}
[TestCase("chrome", ExpectedResult = true)]
[TestCase("CHROME", ExpectedResult = true)]
[TestCase("Google", ExpectedResult = true)]
[TestCase("Google Chrome", ExpectedResult = true)]
[TestCase("Google Chrome twitter", ExpectedResult = false)]
public bool ChromeWebSitesShouldBeFilteredWhenSearchingForChrome(string query)
{
return _twitterChromePwa.FilterWebApplication(query);
}
[TestCase("twitter", 0, ExpectedResult = false)]
[TestCase("Twit", 0, ExpectedResult = false)]
[TestCase("TWITTER", 0, ExpectedResult = false)]
[TestCase("web", 1, ExpectedResult = false)]
[TestCase("Page", 1, ExpectedResult = false)]
[TestCase("WEB PAGE", 1, ExpectedResult = false)]
[TestCase("edge", 2, ExpectedResult = false)]
[TestCase("EDGE", 2, ExpectedResult = false)]
public bool PinnedWebPagesShouldNotBeFilteredWhenSearchingForThem(string query, int scenario)
{
const int CASE_TWITTER = 0;
const int CASE_WEB_PAGE = 1;
const int CASE_EDGE_NAMED_WEBPAGE = 2;
// If the query is a part of the name of the web application, it should not be filtered,
// even if the name is the same as that of the main application, eg: case 2 - edge
switch (scenario)
{
case CASE_TWITTER:
return _twitterChromePwa.FilterWebApplication(query);
case CASE_WEB_PAGE:
return _pinnedWebpage.FilterWebApplication(query);
case CASE_EDGE_NAMED_WEBPAGE:
return _edgeNamedPinnedWebpage.FilterWebApplication(query);
default:
break;
}
// unreachable code
return true;
}
[TestCase("Command Prompt")]
[TestCase("cmd")]
[TestCase("cmd.exe")]
[TestCase("ignoreQueryText")]
public void Win32ApplicationsShouldNotBeFilteredWhenFilteringRunCommands(string query)
{
// Even if there is an exact match in the name or exe name, win32 applications should never be filtered
Assert.IsTrue(_commandPrompt.QueryEqualsNameForRunCommands(query));
}
[TestCase("cmd")]
[TestCase("Cmd")]
[TestCase("CMD")]
public void RunCommandsShouldNotBeFilteredOnExactMatch(string query)
{
// Partial matches should be filtered as cmd is not equal to cmder
Assert.IsFalse(_cmderRunCommand.QueryEqualsNameForRunCommands(query));
// the query matches the name (cmd) and is therefore not filtered (case-insensitive)
Assert.IsTrue(_cmdRunCommand.QueryEqualsNameForRunCommands(query));
}
[Test]
public void WebApplicationShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
{
// Arrange
var mock = new Mock<IPublicAPI>();
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
// Act
List<ContextMenuResult> contextMenuResults = _pinnedWebpage.ContextMenus(mock.Object);
// Assert
Assert.AreEqual(contextMenuResults.Count, 3);
mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
}
[Test]
public void InternetShortcutApplicationShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
{
// Arrange
var mock = new Mock<IPublicAPI>();
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
// Act
List<ContextMenuResult> contextMenuResults = _dummyInternetShortcutApp.ContextMenus(mock.Object);
// Assert
Assert.AreEqual(contextMenuResults.Count, 2);
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
}
[Test]
public void Win32ApplicationShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
{
// Arrange
var mock = new Mock<IPublicAPI>();
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
// Act
List<ContextMenuResult> contextMenuResults = _chrome.ContextMenus(mock.Object);
// Assert
Assert.AreEqual(contextMenuResults.Count, 3);
mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
}
[Test]
public void RunCommandShouldReturnContextMenuWithOpenInConsoleWhenContextMenusIsCalled()
{
// Arrange
var mock = new Mock<IPublicAPI>();
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
// Act
List<ContextMenuResult> contextMenuResults = _cmdRunCommand.ContextMenus(mock.Object);
// Assert
Assert.AreEqual(contextMenuResults.Count, 3);
mock.Verify(x => x.GetTranslation("wox_plugin_program_run_as_administrator"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_containing_folder"), Times.Once());
mock.Verify(x => x.GetTranslation("wox_plugin_program_open_in_console"), Times.Once());
}
[Test]
public void Win32AppsShouldSetNameAsTitleWhileCreatingResult()
{
var mock = new Mock<IPublicAPI>();
mock.Setup(x => x.GetTranslation(It.IsAny<string>())).Returns(It.IsAny<string>());
StringMatcher.Instance = new StringMatcher();
// Act
var result = _cmderRunCommand.Result("cmder", mock.Object);
// Assert
Assert.IsTrue(result.Title.Equals(_cmderRunCommand.Name, StringComparison.Ordinal));
Assert.IsFalse(result.Title.Equals(_cmderRunCommand.Description, StringComparison.Ordinal));
}
}
}

View File

@@ -1,136 +1,136 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
using NUnit.Framework;
using Wox.Infrastructure.Storage;
namespace Microsoft.Plugin.Program.UnitTests.Storage
{
[TestFixture]
public class ListRepositoryTests
{
[Test]
public void ContainsShouldReturnTrueWhenListIsInitializedWithItem()
{
// Arrange
var itemName = "originalItem1";
IRepository<string> repository = new ListRepository<string>() { itemName };
// Act
var result = repository.Contains(itemName);
// Assert
Assert.IsTrue(result);
}
[Test]
public void ContainsShouldReturnTrueWhenListIsUpdatedWithAdd()
{
// Arrange
IRepository<string> repository = new ListRepository<string>();
// Act
var itemName = "newItem";
repository.Add(itemName);
var result = repository.Contains(itemName);
// Assert
Assert.IsTrue(result);
}
[Test]
public void ContainsShouldReturnFalseWhenListIsUpdatedWithRemove()
{
// Arrange
var itemName = "originalItem1";
IRepository<string> repository = new ListRepository<string>() { itemName };
// Act
repository.Remove(itemName);
var result = repository.Contains(itemName);
// Assert
Assert.IsFalse(result);
}
[Test]
public async Task AddShouldNotThrowWhenBeingIterated()
{
// Arrange
ListRepository<string> repository = new ListRepository<string>();
var numItems = 1000;
for (var i = 0; i < numItems; ++i)
{
repository.Add($"OriginalItem_{i}");
}
// Act - Begin iterating on one thread
var iterationTask = Task.Run(() =>
{
var remainingIterations = 10000;
while (remainingIterations > 0)
{
foreach (var item in repository)
{
// keep iterating
}
--remainingIterations;
}
});
// Act - Insert on another thread
var addTask = Task.Run(() =>
{
for (var i = 0; i < numItems; ++i)
{
repository.Add($"NewItem_{i}");
}
});
// Assert that this does not throw. Collections that aren't syncronized will throw an invalidoperatioexception if the list is modified while enumerating
await Task.WhenAll(new Task[] { iterationTask, addTask }).ConfigureAwait(false);
}
[Test]
public async Task RemoveShouldNotThrowWhenBeingIterated()
{
// Arrange
ListRepository<string> repository = new ListRepository<string>();
var numItems = 1000;
for (var i = 0; i < numItems; ++i)
{
repository.Add($"OriginalItem_{i}");
}
// Act - Begin iterating on one thread
var iterationTask = Task.Run(() =>
{
var remainingIterations = 10000;
while (remainingIterations > 0)
{
foreach (var item in repository)
{
// keep iterating
}
--remainingIterations;
}
});
// Act - Remove on another thread
var addTask = Task.Run(() =>
{
for (var i = 0; i < numItems; ++i)
{
repository.Remove($"OriginalItem_{i}");
}
});
// Assert that this does not throw. Collections that aren't syncronized will throw an invalidoperatioexception if the list is modified while enumerating
await Task.WhenAll(new Task[] { iterationTask, addTask }).ConfigureAwait(false);
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
using NUnit.Framework;
using Wox.Infrastructure.Storage;
namespace Microsoft.Plugin.Program.UnitTests.Storage
{
[TestFixture]
public class ListRepositoryTests
{
[Test]
public void ContainsShouldReturnTrueWhenListIsInitializedWithItem()
{
// Arrange
var itemName = "originalItem1";
IRepository<string> repository = new ListRepository<string>() { itemName };
// Act
var result = repository.Contains(itemName);
// Assert
Assert.IsTrue(result);
}
[Test]
public void ContainsShouldReturnTrueWhenListIsUpdatedWithAdd()
{
// Arrange
IRepository<string> repository = new ListRepository<string>();
// Act
var itemName = "newItem";
repository.Add(itemName);
var result = repository.Contains(itemName);
// Assert
Assert.IsTrue(result);
}
[Test]
public void ContainsShouldReturnFalseWhenListIsUpdatedWithRemove()
{
// Arrange
var itemName = "originalItem1";
IRepository<string> repository = new ListRepository<string>() { itemName };
// Act
repository.Remove(itemName);
var result = repository.Contains(itemName);
// Assert
Assert.IsFalse(result);
}
[Test]
public async Task AddShouldNotThrowWhenBeingIterated()
{
// Arrange
ListRepository<string> repository = new ListRepository<string>();
var numItems = 1000;
for (var i = 0; i < numItems; ++i)
{
repository.Add($"OriginalItem_{i}");
}
// Act - Begin iterating on one thread
var iterationTask = Task.Run(() =>
{
var remainingIterations = 10000;
while (remainingIterations > 0)
{
foreach (var item in repository)
{
// keep iterating
}
--remainingIterations;
}
});
// Act - Insert on another thread
var addTask = Task.Run(() =>
{
for (var i = 0; i < numItems; ++i)
{
repository.Add($"NewItem_{i}");
}
});
// Assert that this does not throw. Collections that aren't syncronized will throw an invalidoperatioexception if the list is modified while enumerating
await Task.WhenAll(new Task[] { iterationTask, addTask }).ConfigureAwait(false);
}
[Test]
public async Task RemoveShouldNotThrowWhenBeingIterated()
{
// Arrange
ListRepository<string> repository = new ListRepository<string>();
var numItems = 1000;
for (var i = 0; i < numItems; ++i)
{
repository.Add($"OriginalItem_{i}");
}
// Act - Begin iterating on one thread
var iterationTask = Task.Run(() =>
{
var remainingIterations = 10000;
while (remainingIterations > 0)
{
foreach (var item in repository)
{
// keep iterating
}
--remainingIterations;
}
});
// Act - Remove on another thread
var addTask = Task.Run(() =>
{
for (var i = 0; i < numItems; ++i)
{
repository.Remove($"OriginalItem_{i}");
}
});
// Assert that this does not throw. Collections that aren't syncronized will throw an invalidoperatioexception if the list is modified while enumerating
await Task.WhenAll(new Task[] { iterationTask, addTask }).ConfigureAwait(false);
}
}
}

View File

@@ -1,10 +1,10 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.Plugin.Program.UnitTests.Storage
{
public class PackageRepositoryTest
{
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.Plugin.Program.UnitTests.Storage
{
public class PackageRepositoryTest
{
}
}

View File

@@ -1,14 +1,14 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Linq;
using System.Windows;
using System.Windows.Forms;
using Microsoft.Plugin.Program.Views;
using Wox.Plugin;
namespace Microsoft.Plugin.Program
{
/// <summary>
@@ -39,8 +39,8 @@ namespace Microsoft.Plugin.Program
private void BrowseButton_Click(object sender, RoutedEventArgs e)
{
using (var dialog = new FolderBrowserDialog())
{
using (var dialog = new FolderBrowserDialog())
{
DialogResult result = dialog.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK)
{
@@ -57,7 +57,7 @@ namespace Microsoft.Plugin.Program
System.Windows.MessageBox.Show(_context.API.GetTranslation("wox_plugin_program_invalid_path"));
return;
}
if (_editing == null)
{
if (!ProgramSetting.ProgramSettingDisplayList.Any(x => x.UniqueIdentifier == Directory.Text))

View File

@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.IO;
@@ -32,18 +32,18 @@ namespace Microsoft.Plugin.Program.Logger
}
var configuration = new LoggingConfiguration();
using (var target = new FileTarget())
using (var target = new FileTarget())
{
configuration.AddTarget("file", target);
target.FileName = path.Replace(@"\", "/", StringComparison.Ordinal) + "/${shortdate}.txt";
target.FileName = path.Replace(@"\", "/", StringComparison.Ordinal) + "/${shortdate}.txt";
#if DEBUG
var rule = new LoggingRule("*", LogLevel.Debug, target);
var rule = new LoggingRule("*", LogLevel.Debug, target);
#else
var rule = new LoggingRule("*", LogLevel.Error, target);
#endif
configuration.LoggingRules.Add(rule);
}
}
LogManager.Configuration = configuration;
}
@@ -90,7 +90,7 @@ namespace Microsoft.Plugin.Program.Logger
innerExceptionNumber++;
e = e.InnerException;
}
}
while (e != null);
logger.Error("------------- END Microsoft.Plugin.Program exception -------------");
@@ -121,16 +121,16 @@ namespace Microsoft.Plugin.Program.Logger
private static bool IsKnownWinProgramError(Exception e, string callingMethodName)
{
if (e.TargetSite?.Name == "GetDescription" && callingMethodName == "LnkProgram")
{
return true;
}
if (e is SecurityException || e is UnauthorizedAccessException || e is DirectoryNotFoundException)
{
return true;
}
if (e.TargetSite?.Name == "GetDescription" && callingMethodName == "LnkProgram")
{
return true;
}
if (e is SecurityException || e is UnauthorizedAccessException || e is DirectoryNotFoundException)
{
return true;
}
return false;
}
@@ -138,17 +138,17 @@ namespace Microsoft.Plugin.Program.Logger
{
if (((e.HResult == -2147024774 || e.HResult == -2147009769) && callingMethodName == "ResourceFromPri")
|| (e.HResult == -2147024894 && (callingMethodName == "LogoPathFromUri" || callingMethodName == "ImageFromPath"))
|| (e.HResult == -2147024864 && callingMethodName == "InitializeAppInfo"))
{
return true;
}
if (callingMethodName == "XmlNamespaces")
{
return true;
}
|| (e.HResult == -2147024864 && callingMethodName == "InitializeAppInfo"))
{
return true;
}
if (callingMethodName == "XmlNamespaces")
{
return true;
}
return false;
}
}
}
}

View File

@@ -1,184 +1,184 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Plugin.Program.Programs;
using Microsoft.Plugin.Program.Storage;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.Storage;
using Wox.Plugin;
using Stopwatch = Wox.Infrastructure.Stopwatch;
namespace Microsoft.Plugin.Program
{
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Plugin.Program.Programs;
using Microsoft.Plugin.Program.Storage;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.Storage;
using Wox.Plugin;
using Stopwatch = Wox.Infrastructure.Stopwatch;
namespace Microsoft.Plugin.Program
{
public class Main : IPlugin, IPluginI18n, IContextMenu, ISavable, IReloadable, IDisposable
{
internal static ProgramPluginSettings Settings { get; set; }
private static bool IsStartupIndexProgramsRequired => Settings.LastIndexTime.AddDays(3) < DateTime.Today;
private static PluginInitContext _context;
private readonly PluginJsonStorage<ProgramPluginSettings> _settingsStorage;
{
internal static ProgramPluginSettings Settings { get; set; }
private static bool IsStartupIndexProgramsRequired => Settings.LastIndexTime.AddDays(3) < DateTime.Today;
private static PluginInitContext _context;
private readonly PluginJsonStorage<ProgramPluginSettings> _settingsStorage;
private bool _disposed = false;
private PackageRepository _packageRepository = new PackageRepository(new PackageCatalogWrapper(), new BinaryStorage<IList<UWPApplication>>("UWP"));
private static Win32ProgramFileSystemWatchers _win32ProgramRepositoryHelper;
private static Win32ProgramRepository _win32ProgramRepository;
public Main()
{
_settingsStorage = new PluginJsonStorage<ProgramPluginSettings>();
Settings = _settingsStorage.Load();
// This helper class initializes the file system watchers based on the locations to watch
_win32ProgramRepositoryHelper = new Win32ProgramFileSystemWatchers();
// Initialize the Win32ProgramRepository with the settings object
_win32ProgramRepository = new Win32ProgramRepository(_win32ProgramRepositoryHelper.FileSystemWatchers.Cast<IFileSystemWatcherWrapper>().ToList(), new BinaryStorage<IList<Programs.Win32Program>>("Win32"), Settings, _win32ProgramRepositoryHelper.PathsToWatch);
Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Preload programs cost", () =>
{
_win32ProgramRepository.Load();
_packageRepository.Load();
});
Log.Info($"|Microsoft.Plugin.Program.Main|Number of preload win32 programs <{_win32ProgramRepository.Count()}>");
var a = Task.Run(() =>
{
if (IsStartupIndexProgramsRequired || !_win32ProgramRepository.Any())
{
Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Win32Program index cost", _win32ProgramRepository.IndexPrograms);
}
});
var b = Task.Run(() =>
{
if (IsStartupIndexProgramsRequired || !_packageRepository.Any())
{
Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Win32Program index cost", _packageRepository.IndexPrograms);
}
});
Task.WaitAll(a, b);
Settings.LastIndexTime = DateTime.Today;
}
public void Save()
{
_settingsStorage.Save();
_win32ProgramRepository.Save();
_packageRepository.Save();
}
public List<Result> Query(Query query)
{
var results1 = _win32ProgramRepository.AsParallel()
.Where(p => p.Enabled)
.Select(p => p.Result(query.Search, _context.API));
var results2 = _packageRepository.AsParallel()
.Where(p => p.Enabled)
.Select(p => p.Result(query.Search, _context.API));
var result = results1.Concat(results2).Where(r => r != null && r.Score > 0);
var maxScore = result.Max(x => x.Score);
result = result.Where(x => x.Score > Settings.MinScoreThreshold * maxScore);
return result.ToList();
}
public void Init(PluginInitContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_context.API.ThemeChanged += OnThemeChanged;
UpdateUWPIconPath(_context.API.GetCurrentTheme());
}
public void OnThemeChanged(Theme currentTheme, Theme newTheme)
{
UpdateUWPIconPath(newTheme);
}
public void UpdateUWPIconPath(Theme theme)
{
foreach (UWPApplication app in _packageRepository)
{
app.UpdatePath(theme);
}
}
public void IndexPrograms()
{
var t1 = Task.Run(() => _win32ProgramRepository.IndexPrograms());
var t2 = Task.Run(() => _packageRepository.IndexPrograms());
Task.WaitAll(t1, t2);
Settings.LastIndexTime = DateTime.Today;
}
public string GetTranslatedPluginTitle()
{
return _context.API.GetTranslation("wox_plugin_program_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return _context.API.GetTranslation("wox_plugin_program_plugin_description");
}
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
if (selectedResult == null)
{
throw new ArgumentNullException(nameof(selectedResult));
}
var menuOptions = new List<ContextMenuResult>();
if (selectedResult.ContextData is Programs.IProgram program)
{
menuOptions = program.ContextMenus(_context.API);
}
return menuOptions;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive and show the user a warning message")]
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
{
try
{
if (runProcess == null)
{
throw new ArgumentNullException(nameof(runProcess));
}
if (info == null)
{
throw new ArgumentNullException(nameof(info));
}
runProcess(info);
}
catch (Exception)
{
var name = "Plugin: Program";
var message = $"Unable to start: {info.FileName}";
_context.API.ShowMsg(name, message, string.Empty);
}
}
public void ReloadData()
{
IndexPrograms();
}
private static Win32ProgramRepository _win32ProgramRepository;
public Main()
{
_settingsStorage = new PluginJsonStorage<ProgramPluginSettings>();
Settings = _settingsStorage.Load();
// This helper class initializes the file system watchers based on the locations to watch
_win32ProgramRepositoryHelper = new Win32ProgramFileSystemWatchers();
// Initialize the Win32ProgramRepository with the settings object
_win32ProgramRepository = new Win32ProgramRepository(_win32ProgramRepositoryHelper.FileSystemWatchers.Cast<IFileSystemWatcherWrapper>().ToList(), new BinaryStorage<IList<Programs.Win32Program>>("Win32"), Settings, _win32ProgramRepositoryHelper.PathsToWatch);
Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Preload programs cost", () =>
{
_win32ProgramRepository.Load();
_packageRepository.Load();
});
Log.Info($"|Microsoft.Plugin.Program.Main|Number of preload win32 programs <{_win32ProgramRepository.Count()}>");
var a = Task.Run(() =>
{
if (IsStartupIndexProgramsRequired || !_win32ProgramRepository.Any())
{
Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Win32Program index cost", _win32ProgramRepository.IndexPrograms);
}
});
var b = Task.Run(() =>
{
if (IsStartupIndexProgramsRequired || !_packageRepository.Any())
{
Stopwatch.Normal("|Microsoft.Plugin.Program.Main|Win32Program index cost", _packageRepository.IndexPrograms);
}
});
Task.WaitAll(a, b);
Settings.LastIndexTime = DateTime.Today;
}
public void Save()
{
_settingsStorage.Save();
_win32ProgramRepository.Save();
_packageRepository.Save();
}
public List<Result> Query(Query query)
{
var results1 = _win32ProgramRepository.AsParallel()
.Where(p => p.Enabled)
.Select(p => p.Result(query.Search, _context.API));
var results2 = _packageRepository.AsParallel()
.Where(p => p.Enabled)
.Select(p => p.Result(query.Search, _context.API));
var result = results1.Concat(results2).Where(r => r != null && r.Score > 0);
var maxScore = result.Max(x => x.Score);
result = result.Where(x => x.Score > Settings.MinScoreThreshold * maxScore);
return result.ToList();
}
public void Init(PluginInitContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_context.API.ThemeChanged += OnThemeChanged;
UpdateUWPIconPath(_context.API.GetCurrentTheme());
}
public void OnThemeChanged(Theme currentTheme, Theme newTheme)
{
UpdateUWPIconPath(newTheme);
}
public void UpdateUWPIconPath(Theme theme)
{
foreach (UWPApplication app in _packageRepository)
{
app.UpdatePath(theme);
}
}
public void IndexPrograms()
{
var t1 = Task.Run(() => _win32ProgramRepository.IndexPrograms());
var t2 = Task.Run(() => _packageRepository.IndexPrograms());
Task.WaitAll(t1, t2);
Settings.LastIndexTime = DateTime.Today;
}
public string GetTranslatedPluginTitle()
{
return _context.API.GetTranslation("wox_plugin_program_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return _context.API.GetTranslation("wox_plugin_program_plugin_description");
}
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
if (selectedResult == null)
{
throw new ArgumentNullException(nameof(selectedResult));
}
var menuOptions = new List<ContextMenuResult>();
if (selectedResult.ContextData is Programs.IProgram program)
{
menuOptions = program.ContextMenus(_context.API);
}
return menuOptions;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive and show the user a warning message")]
public static void StartProcess(Func<ProcessStartInfo, Process> runProcess, ProcessStartInfo info)
{
try
{
if (runProcess == null)
{
throw new ArgumentNullException(nameof(runProcess));
}
if (info == null)
{
throw new ArgumentNullException(nameof(info));
}
runProcess(info);
}
catch (Exception)
{
var name = "Plugin: Program";
var message = $"Unable to start: {info.FileName}";
_context.API.ShowMsg(name, message, string.Empty);
}
}
public void ReloadData()
{
IndexPrograms();
}
public void Dispose()
{
@@ -198,5 +198,5 @@ namespace Microsoft.Plugin.Program
}
}
}
}
}
}
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
@@ -10,11 +10,11 @@ namespace Microsoft.Plugin.Program
public class ProgramPluginSettings
{
public DateTime LastIndexTime { get; set; }
public List<ProgramSource> ProgramSources { get; } = new List<ProgramSource>();
public List<DisabledProgramSource> DisabledProgramSources { get; } = new List<DisabledProgramSource>();
public List<string> ProgramSuffixes { get; } = new List<string>() { "bat", "appref-ms", "exe", "lnk", "url" };
public bool EnableStartMenuSource { get; set; } = true;
@@ -28,5 +28,5 @@ namespace Microsoft.Plugin.Program
public double MinScoreThreshold { get; set; } = 0.75;
internal const char SuffixSeparator = ';';
}
}
}

View File

@@ -1,8 +1,8 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Windows;
using Wox.Plugin;

View File

@@ -1,48 +1,48 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using static Microsoft.Plugin.Program.Programs.UWP;
namespace Microsoft.Plugin.Program.Programs
{
public static class AppxPackageHelper
{
// This function returns a list of attributes of applications
public static List<IAppxManifestApplication> GetAppsFromManifest(IStream stream)
{
List<IAppxManifestApplication> apps = new List<IAppxManifestApplication>();
var appxFactory = new AppxFactory();
var reader = ((IAppxFactory)appxFactory).CreateManifestReader(stream);
var manifestApps = reader.GetApplications();
while (manifestApps.GetHasCurrent())
{
var manifestApp = manifestApps.GetCurrent();
var hr = manifestApp.GetStringValue("AppListEntry", out var appListEntry);
_ = CheckHRAndReturnOrThrow(hr, appListEntry);
if (appListEntry != "none")
{
apps.Add(manifestApp);
}
manifestApps.MoveNext();
}
return apps;
}
public static T CheckHRAndReturnOrThrow<T>(Hresult hr, T result)
{
if (hr != Hresult.Ok)
{
Marshal.ThrowExceptionForHR((int)hr);
}
return result;
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using static Microsoft.Plugin.Program.Programs.UWP;
namespace Microsoft.Plugin.Program.Programs
{
public static class AppxPackageHelper
{
// This function returns a list of attributes of applications
public static List<IAppxManifestApplication> GetAppsFromManifest(IStream stream)
{
List<IAppxManifestApplication> apps = new List<IAppxManifestApplication>();
var appxFactory = new AppxFactory();
var reader = ((IAppxFactory)appxFactory).CreateManifestReader(stream);
var manifestApps = reader.GetApplications();
while (manifestApps.GetHasCurrent())
{
var manifestApp = manifestApps.GetCurrent();
var hr = manifestApp.GetStringValue("AppListEntry", out var appListEntry);
_ = CheckHRAndReturnOrThrow(hr, appListEntry);
if (appListEntry != "none")
{
apps.Add(manifestApp);
}
manifestApps.MoveNext();
}
return apps;
}
public static T CheckHRAndReturnOrThrow<T>(Hresult hr, T result)
{
if (hr != Hresult.Ok)
{
Marshal.ThrowExceptionForHR((int)hr);
}
return result;
}
}
}

View File

@@ -1,18 +1,18 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Windows.ApplicationModel;
using Windows.Foundation;
namespace Microsoft.Plugin.Program.Programs
{
internal interface IPackageCatalog
{
event TypedEventHandler<PackageCatalog, PackageInstallingEventArgs> PackageInstalling;
event TypedEventHandler<PackageCatalog, PackageUninstallingEventArgs> PackageUninstalling;
event TypedEventHandler<PackageCatalog, PackageUpdatingEventArgs> PackageUpdating;
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Windows.ApplicationModel;
using Windows.Foundation;
namespace Microsoft.Plugin.Program.Programs
{
internal interface IPackageCatalog
{
event TypedEventHandler<PackageCatalog, PackageInstallingEventArgs> PackageInstalling;
event TypedEventHandler<PackageCatalog, PackageUninstallingEventArgs> PackageUninstalling;
event TypedEventHandler<PackageCatalog, PackageUpdatingEventArgs> PackageUpdating;
}
}

View File

@@ -1,65 +1,65 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Windows.ApplicationModel;
using Windows.Foundation;
namespace Microsoft.Plugin.Program.Programs
{
/// <summary>
/// This is a simple wrapper class around the PackageCatalog to facilitate unit testing.
/// </summary>
internal class PackageCatalogWrapper : IPackageCatalog
{
private PackageCatalog _packageCatalog;
public PackageCatalogWrapper()
{
// Opens the catalog of packages that is available for the current user.
_packageCatalog = PackageCatalog.OpenForCurrentUser();
}
// Summary: Indicates that an app package is installing.
public event TypedEventHandler<PackageCatalog, PackageInstallingEventArgs> PackageInstalling
{
add
{
_packageCatalog.PackageInstalling += value;
}
remove
{
_packageCatalog.PackageInstalling -= value;
}
}
// Summary: Indicates that an app package is uninstalling.
public event TypedEventHandler<PackageCatalog, PackageUninstallingEventArgs> PackageUninstalling
{
add
{
_packageCatalog.PackageUninstalling += value;
}
remove
{
_packageCatalog.PackageUninstalling -= value;
}
}
// Summary: Indicates that an app package is updating.
public event TypedEventHandler<PackageCatalog, PackageUpdatingEventArgs> PackageUpdating
{
add
{
_packageCatalog.PackageUpdating += value;
}
remove
{
_packageCatalog.PackageUpdating -= value;
}
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Windows.ApplicationModel;
using Windows.Foundation;
namespace Microsoft.Plugin.Program.Programs
{
/// <summary>
/// This is a simple wrapper class around the PackageCatalog to facilitate unit testing.
/// </summary>
internal class PackageCatalogWrapper : IPackageCatalog
{
private PackageCatalog _packageCatalog;
public PackageCatalogWrapper()
{
// Opens the catalog of packages that is available for the current user.
_packageCatalog = PackageCatalog.OpenForCurrentUser();
}
// Summary: Indicates that an app package is installing.
public event TypedEventHandler<PackageCatalog, PackageInstallingEventArgs> PackageInstalling
{
add
{
_packageCatalog.PackageInstalling += value;
}
remove
{
_packageCatalog.PackageInstalling -= value;
}
}
// Summary: Indicates that an app package is uninstalling.
public event TypedEventHandler<PackageCatalog, PackageUninstallingEventArgs> PackageUninstalling
{
add
{
_packageCatalog.PackageUninstalling += value;
}
remove
{
_packageCatalog.PackageUninstalling -= value;
}
}
// Summary: Indicates that an app package is updating.
public event TypedEventHandler<PackageCatalog, PackageUpdatingEventArgs> PackageUpdating
{
add
{
_packageCatalog.PackageUpdating += value;
}
remove
{
_packageCatalog.PackageUpdating -= value;
}
}
}
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using Microsoft.Plugin.Program.Logger;
@@ -23,9 +23,9 @@ namespace Microsoft.Plugin.Program.Programs
public string InstalledLocation { get; } = string.Empty;
public PackageWrapper()
{
}
public PackageWrapper()
{
}
public PackageWrapper(string name, string fullName, string familyName, bool isFramework, bool isDevelopmentMode, string installedLocation)
{
@@ -39,9 +39,9 @@ namespace Microsoft.Plugin.Program.Programs
public static PackageWrapper GetWrapperFromPackage(Package package)
{
if (package == null)
{
throw new ArgumentNullException(nameof(package));
if (package == null)
{
throw new ArgumentNullException(nameof(package));
}
string path;

View File

@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
@@ -24,7 +24,7 @@ namespace Microsoft.Plugin.Program.Programs
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching COM")]
private struct WIN32_FIND_DATAW
{
{
public uint dwFileAttributes;
public long ftCreationTime;
public long ftLastAccessTime;
@@ -39,8 +39,8 @@ namespace Microsoft.Plugin.Program.Programs
public string cAlternateFileName;
}
[Flags]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Represents flags specified in IShellLink interface")]
[Flags]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Represents flags specified in IShellLink interface")]
public enum SLR_FLAGS
{
SLR_NO_UI = 0x1,
@@ -52,71 +52,71 @@ namespace Microsoft.Plugin.Program.Programs
SLR_NOLINKINFO = 0x40,
SLR_INVOKE_MSI = 0x80,
}
// Reference : http://www.pinvoke.net/default.aspx/Interfaces.IShellLinkW
// The IShellLink interface allows Shell links to be created, modified, and resolved
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214F9-0000-0000-C000-000000000046")]
private interface IShellLinkW
{
/// <summary>Retrieves the path and file name of a Shell link object</summary>
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, ref WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags);
/// <summary>Retrieves the list of item identifiers for a Shell link object</summary>
void GetIDList(out IntPtr ppidl);
/// <summary>Sets the pointer to an item identifier list (PIDL) for a Shell link object.</summary>
void SetIDList(IntPtr pidl);
/// <summary>Retrieves the description string for a Shell link object</summary>
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
/// <summary>Sets the description for a Shell link object. The description can be any application-defined string</summary>
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
/// <summary>Retrieves the name of the working directory for a Shell link object</summary>
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
/// <summary>Sets the name of the working directory for a Shell link object</summary>
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
/// <summary>Retrieves the command-line arguments associated with a Shell link object</summary>
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
/// <summary>Sets the command-line arguments for a Shell link object</summary>
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
/// <summary>Retrieves the hot key for a Shell link object</summary>
void GetHotkey(out short pwHotkey);
/// <summary>Sets a hot key for a Shell link object</summary>
void SetHotkey(short wHotkey);
/// <summary>Retrieves the show command for a Shell link object</summary>
void GetShowCmd(out int piShowCmd);
/// <summary>Sets the show command for a Shell link object. The show command sets the initial show state of the window.</summary>
void SetShowCmd(int iShowCmd);
/// <summary>Retrieves the location (path and index) of the icon for a Shell link object</summary>
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
/// <summary>Sets the location (path and index) of the icon for a Shell link object</summary>
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
/// <summary>Sets the relative path to the Shell link object</summary>
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
/// <summary>Attempts to find the target of a Shell link, even if it has been moved or renamed</summary>
void Resolve(ref Accessibility._RemotableHandle hwnd, SLR_FLAGS fFlags);
/// <summary>Sets the path and file name of a Shell link object</summary>
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
}
[ComImport]
[ComImport]
[Guid("00021401-0000-0000-C000-000000000046")]
private class ShellLink
{
@@ -127,7 +127,7 @@ namespace Microsoft.Plugin.Program.Programs
// Contains the arguments to the app
public string Arguments { get; set; } = string.Empty;
public bool HasArguments { get; set; } = false;
// Retrieve the target path using Shell Link
@@ -173,8 +173,8 @@ namespace Microsoft.Plugin.Program.Programs
HasArguments = true;
}
}
return target;
}
}
}
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
@@ -10,20 +10,20 @@ using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Xml.Linq;
using Microsoft.Plugin.Program.Logger;
using Microsoft.Plugin.Program.Win32;
using Wox.Infrastructure.Logger;
using Microsoft.Plugin.Program.Win32;
using Wox.Infrastructure.Logger;
namespace Microsoft.Plugin.Program.Programs
{
[Serializable]
public partial class UWP
{
public string Name { get; }
public string FullName { get; }
public string FamilyName { get; }
public string FamilyName { get; }
public string Location { get; set; }
public IList<UWPApplication> Apps { get; private set; }
@@ -33,12 +33,12 @@ namespace Microsoft.Plugin.Program.Programs
public static IPackageManager PackageManagerWrapper { get; set; } = new PackageManagerWrapper();
public UWP(IPackage package)
{
if (package == null)
{
throw new ArgumentNullException(nameof(package));
}
{
if (package == null)
{
throw new ArgumentNullException(nameof(package));
}
Name = package.Name;
FullName = package.FullName;
FamilyName = package.FamilyName;
@@ -58,36 +58,36 @@ namespace Microsoft.Plugin.Program.Programs
if (hResult == Hresult.Ok)
{
var apps = new List<UWPApplication>();
var apps = new List<UWPApplication>();
List<IAppxManifestApplication> appsViaManifests = AppxPackageHelper.GetAppsFromManifest(stream);
foreach (var appInManifest in appsViaManifests)
{
var app = new UWPApplication(appInManifest, this);
apps.Add(app);
}
Apps = apps.Where(a =>
{
var valid =
!string.IsNullOrEmpty(a.UserModelId) &&
!string.IsNullOrEmpty(a.DisplayName) &&
}
Apps = apps.Where(a =>
{
var valid =
!string.IsNullOrEmpty(a.UserModelId) &&
!string.IsNullOrEmpty(a.DisplayName) &&
a.AppListEntry != "none";
return valid;
return valid;
}).ToArray();
}
else
{
var e = Marshal.GetExceptionForHR((int)hResult);
ProgramLogger.LogException(
ProgramLogger.LogException(
$"|UWP|InitializeAppInfo|{path}" +
"|Error caused while trying to get the details of the UWP program", e);
Apps = new List<UWPApplication>().ToArray();
}
}
// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx
private static string[] XmlNamespaces(string path)
{
@@ -128,15 +128,15 @@ namespace Microsoft.Plugin.Program.Programs
}
}
ProgramLogger.LogException(
ProgramLogger.LogException(
$"|UWP|XmlNamespaces|{Location}" +
"|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version "
+ $"{FullName} from location {Location} is returned.", new FormatException());
Version = PackageVersion.Unknown;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive.")]
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive.")]
public static UWPApplication[] All()
{
var windows10 = new Version(10, 0);
@@ -153,12 +153,12 @@ namespace Microsoft.Plugin.Program.Programs
}
catch (Exception e)
{
ProgramLogger.LogException(
ProgramLogger.LogException(
$"|UWP|All|{p.InstalledLocation}|An unexpected error occurred and "
+ $"unable to convert Package to UWP for {p.FullName}", e);
return Array.Empty<UWPApplication>();
}
return u.Apps;
}).ToArray();
@@ -192,8 +192,8 @@ namespace Microsoft.Plugin.Program.Programs
{
ProgramLogger.LogException("UWP", "CurrentUserPackages", $"id", "An unexpected error occurred and unable to verify if package is valid", e);
return false;
}
}
return valid;
});
@@ -230,8 +230,8 @@ namespace Microsoft.Plugin.Program.Programs
Unknown,
}
[Flags]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1714:Flags enums should have plural names", Justification = "This name is consistent with the corresponding win32 flags: https://docs.microsoft.com/en-us/windows/win32/stg/stgm-constants ")]
[Flags]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1714:Flags enums should have plural names", Justification = "This name is consistent with the corresponding win32 flags: https://docs.microsoft.com/en-us/windows/win32/stg/stgm-constants ")]
public enum Stgm : long
{
Read = 0x00000000L,
@@ -240,6 +240,6 @@ namespace Microsoft.Plugin.Program.Programs
public enum Hresult : int
{
Ok = 0x0,
}
}
}
}
}

View File

@@ -1,14 +1,14 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
@@ -16,481 +16,481 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Plugin.Program.Logger;
using Microsoft.Plugin.Program.Win32;
using Microsoft.Plugin.Program.Win32;
using Wox.Infrastructure;
using Wox.Infrastructure.Image;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.Image;
using Wox.Infrastructure.Logger;
using Wox.Plugin;
using Wox.Plugin.SharedCommands;
using static Microsoft.Plugin.Program.Programs.UWP;
namespace Microsoft.Plugin.Program.Programs
{
[Serializable]
public class UWPApplication : IProgram
{
public string AppListEntry { get; set; }
public string UniqueIdentifier { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public string UserModelId { get; set; }
public string BackgroundColor { get; set; }
public string EntryPoint { get; set; }
public string Name => DisplayName;
public string Location => Package.Location;
public bool Enabled { get; set; }
public bool CanRunElevated { get; set; }
public string LogoPath { get; set; }
public UWP Package { get; set; }
private string logoUri;
// Function to calculate the score of a result
private int Score(string query)
{
var displayNameMatch = StringMatcher.FuzzySearch(query, DisplayName);
var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
var score = new[] { displayNameMatch.Score, descriptionMatch.Score / 2 }.Max();
return score;
}
// Function to set the subtitle based on the Type of application
private static string SetSubtitle(IPublicAPI api)
{
return api.GetTranslation("powertoys_run_plugin_program_packaged_application");
}
public Result Result(string query, IPublicAPI api)
{
if (api == null)
{
throw new ArgumentNullException(nameof(api));
}
var score = Score(query);
if (score <= 0)
{ // no need to create result if score is 0
return null;
}
var result = new Result
{
SubTitle = SetSubtitle(api),
Icon = Logo,
Score = score,
ContextData = this,
Action = e =>
{
Launch(api);
return true;
},
};
// To set the title to always be the displayname of the packaged application
result.Title = DisplayName;
result.TitleHighlightData = StringMatcher.FuzzySearch(query, Name).MatchData;
var toolTipTitle = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_name"), result.Title);
var toolTipText = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_path"), Package.Location);
result.ToolTipData = new ToolTipData(toolTipTitle, toolTipText);
return result;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive.")]
public List<ContextMenuResult> ContextMenus(IPublicAPI api)
{
if (api == null)
{
throw new ArgumentNullException(nameof(api));
}
var contextMenus = new List<ContextMenuResult>();
if (CanRunElevated)
{
contextMenus.Add(
new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = api.GetTranslation("wox_plugin_program_run_as_administrator"),
Glyph = "\xE7EF",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.Enter,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = _ =>
{
string command = "shell:AppsFolder\\" + UniqueIdentifier;
command = Environment.ExpandEnvironmentVariables(command.Trim());
var info = ShellCommand.SetProcessStartInfo(command, verb: "runas");
info.UseShellExecute = true;
Process.Start(info);
return true;
},
});
}
contextMenus.Add(
new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = api.GetTranslation("wox_plugin_program_open_containing_folder"),
Glyph = "\xE838",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.E,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = _ =>
{
Main.StartProcess(Process.Start, new ProcessStartInfo("explorer", Package.Location));
return true;
},
});
contextMenus.Add(new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = api.GetTranslation("wox_plugin_program_open_in_console"),
Glyph = "\xE756",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.C,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = (context) =>
{
try
{
Helper.OpenInConsole(Package.Location);
return true;
}
catch (Exception e)
{
Log.Exception($"|Microsoft.Plugin.Program.UWP.ContextMenu| Failed to open {Name} in console, {e.Message}", e);
return false;
}
},
});
return contextMenus;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive, and showing the user an error message")]
private async void Launch(IPublicAPI api)
{
var appManager = new ApplicationActivationHelper.ApplicationActivationManager();
const string noArgs = "";
const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None;
await Task.Run(() =>
{
try
{
appManager.ActivateApplication(UserModelId, noArgs, noFlags, out uint unusedPid);
}
catch (Exception)
{
var name = "Plugin: Program";
var message = $"Can't start UWP: {DisplayName}";
api.ShowMsg(name, message, string.Empty);
}
}).ConfigureAwait(false);
}
public UWPApplication(IAppxManifestApplication manifestApp, UWP package)
{
if (manifestApp == null)
{
throw new ArgumentNullException(nameof(manifestApp));
}
var hr = manifestApp.GetAppUserModelId(out var tmpUserModelId);
UserModelId = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUserModelId);
hr = manifestApp.GetAppUserModelId(out var tmpUniqueIdentifier);
UniqueIdentifier = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUniqueIdentifier);
hr = manifestApp.GetStringValue("DisplayName", out var tmpDisplayName);
DisplayName = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDisplayName);
hr = manifestApp.GetStringValue("Description", out var tmpDescription);
Description = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDescription);
hr = manifestApp.GetStringValue("BackgroundColor", out var tmpBackgroundColor);
BackgroundColor = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpBackgroundColor);
hr = manifestApp.GetStringValue("EntryPoint", out var tmpEntryPoint);
EntryPoint = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpEntryPoint);
using static Microsoft.Plugin.Program.Programs.UWP;
Package = package ?? throw new ArgumentNullException(nameof(package));
DisplayName = ResourceFromPri(package.FullName, DisplayName);
Description = ResourceFromPri(package.FullName, Description);
logoUri = LogoUriFromManifest(manifestApp);
Enabled = true;
CanRunElevated = IfApplicationcanRunElevated();
}
private bool IfApplicationcanRunElevated()
{
if (EntryPoint == "Windows.FullTrustApplication")
{
return true;
}
else
{
var manifest = Package.Location + "\\AppxManifest.xml";
if (File.Exists(manifest))
{
var file = File.ReadAllText(manifest);
if (file.Contains("TrustLevel=\"mediumIL\"", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
return false;
}
internal string ResourceFromPri(string packageFullName, string resourceReference)
{
const string prefix = "ms-resource:";
if (!string.IsNullOrWhiteSpace(resourceReference) && resourceReference.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
// magic comes from @talynone
// https://github.com/talynone/Wox.Plugin.WindowsUniversalAppLauncher/blob/master/StoreAppLauncher/Helpers/NativeApiHelper.cs#L139-L153
string key = resourceReference.Substring(prefix.Length);
string parsed;
if (key.StartsWith("//", StringComparison.Ordinal))
{
parsed = prefix + key;
}
else if (key.StartsWith("/", StringComparison.Ordinal))
{
parsed = prefix + "//" + key;
}
else if (key.Contains("resources", StringComparison.OrdinalIgnoreCase))
{
parsed = prefix + key;
}
else
{
parsed = prefix + "///resources/" + key;
}
var outBuffer = new StringBuilder(128);
string source = $"@{{{packageFullName}? {parsed}}}";
var capacity = (uint)outBuffer.Capacity;
var hResult = NativeMethods.SHLoadIndirectString(source, outBuffer, capacity, IntPtr.Zero);
if (hResult == Hresult.Ok)
{
var loaded = outBuffer.ToString();
if (!string.IsNullOrEmpty(loaded))
{
return loaded;
}
else
{
ProgramLogger.LogException(
$"|UWP|ResourceFromPri|{Package.Location}|Can't load null or empty result "
+ $"pri {source} in uwp location {Package.Location}", new NullReferenceException());
return string.Empty;
}
}
else
{
// https://github.com/Wox-launcher/Wox/issues/964
// known hresult 2147942522:
// 'Microsoft Corporation' violates pattern constraint of '\bms-resource:.{1,256}'.
// for
// Microsoft.MicrosoftOfficeHub_17.7608.23501.0_x64__8wekyb3d8bbwe: ms-resource://Microsoft.MicrosoftOfficeHub/officehubintl/AppManifest_GetOffice_Description
// Microsoft.BingFoodAndDrink_3.0.4.336_x64__8wekyb3d8bbwe: ms-resource:AppDescription
var e = Marshal.GetExceptionForHR((int)hResult);
ProgramLogger.LogException($"|UWP|ResourceFromPri|{Package.Location}|Load pri failed {source} with HResult {hResult} and location {Package.Location}", e);
return string.Empty;
}
}
else
{
return resourceReference;
}
}
internal string LogoUriFromManifest(IAppxManifestApplication app)
{
namespace Microsoft.Plugin.Program.Programs
{
[Serializable]
public class UWPApplication : IProgram
{
public string AppListEntry { get; set; }
public string UniqueIdentifier { get; set; }
public string DisplayName { get; set; }
public string Description { get; set; }
public string UserModelId { get; set; }
public string BackgroundColor { get; set; }
public string EntryPoint { get; set; }
public string Name => DisplayName;
public string Location => Package.Location;
public bool Enabled { get; set; }
public bool CanRunElevated { get; set; }
public string LogoPath { get; set; }
public UWP Package { get; set; }
private string logoUri;
// Function to calculate the score of a result
private int Score(string query)
{
var displayNameMatch = StringMatcher.FuzzySearch(query, DisplayName);
var descriptionMatch = StringMatcher.FuzzySearch(query, Description);
var score = new[] { displayNameMatch.Score, descriptionMatch.Score / 2 }.Max();
return score;
}
// Function to set the subtitle based on the Type of application
private static string SetSubtitle(IPublicAPI api)
{
return api.GetTranslation("powertoys_run_plugin_program_packaged_application");
}
public Result Result(string query, IPublicAPI api)
{
if (api == null)
{
throw new ArgumentNullException(nameof(api));
}
var score = Score(query);
if (score <= 0)
{ // no need to create result if score is 0
return null;
}
var result = new Result
{
SubTitle = SetSubtitle(api),
Icon = Logo,
Score = score,
ContextData = this,
Action = e =>
{
Launch(api);
return true;
},
};
// To set the title to always be the displayname of the packaged application
result.Title = DisplayName;
result.TitleHighlightData = StringMatcher.FuzzySearch(query, Name).MatchData;
var toolTipTitle = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_name"), result.Title);
var toolTipText = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_path"), Package.Location);
result.ToolTipData = new ToolTipData(toolTipTitle, toolTipText);
return result;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive.")]
public List<ContextMenuResult> ContextMenus(IPublicAPI api)
{
if (api == null)
{
throw new ArgumentNullException(nameof(api));
}
var contextMenus = new List<ContextMenuResult>();
if (CanRunElevated)
{
contextMenus.Add(
new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = api.GetTranslation("wox_plugin_program_run_as_administrator"),
Glyph = "\xE7EF",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.Enter,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = _ =>
{
string command = "shell:AppsFolder\\" + UniqueIdentifier;
command = Environment.ExpandEnvironmentVariables(command.Trim());
var info = ShellCommand.SetProcessStartInfo(command, verb: "runas");
info.UseShellExecute = true;
Process.Start(info);
return true;
},
});
}
contextMenus.Add(
new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = api.GetTranslation("wox_plugin_program_open_containing_folder"),
Glyph = "\xE838",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.E,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = _ =>
{
Main.StartProcess(Process.Start, new ProcessStartInfo("explorer", Package.Location));
return true;
},
});
contextMenus.Add(new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = api.GetTranslation("wox_plugin_program_open_in_console"),
Glyph = "\xE756",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.C,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = (context) =>
{
try
{
Helper.OpenInConsole(Package.Location);
return true;
}
catch (Exception e)
{
Log.Exception($"|Microsoft.Plugin.Program.UWP.ContextMenu| Failed to open {Name} in console, {e.Message}", e);
return false;
}
},
});
return contextMenus;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive, and showing the user an error message")]
private async void Launch(IPublicAPI api)
{
var appManager = new ApplicationActivationHelper.ApplicationActivationManager();
const string noArgs = "";
const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None;
await Task.Run(() =>
{
try
{
appManager.ActivateApplication(UserModelId, noArgs, noFlags, out uint unusedPid);
}
catch (Exception)
{
var name = "Plugin: Program";
var message = $"Can't start UWP: {DisplayName}";
api.ShowMsg(name, message, string.Empty);
}
}).ConfigureAwait(false);
}
public UWPApplication(IAppxManifestApplication manifestApp, UWP package)
{
if (manifestApp == null)
{
throw new ArgumentNullException(nameof(manifestApp));
}
var hr = manifestApp.GetAppUserModelId(out var tmpUserModelId);
UserModelId = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUserModelId);
hr = manifestApp.GetAppUserModelId(out var tmpUniqueIdentifier);
UniqueIdentifier = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUniqueIdentifier);
hr = manifestApp.GetStringValue("DisplayName", out var tmpDisplayName);
DisplayName = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDisplayName);
hr = manifestApp.GetStringValue("Description", out var tmpDescription);
Description = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDescription);
hr = manifestApp.GetStringValue("BackgroundColor", out var tmpBackgroundColor);
BackgroundColor = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpBackgroundColor);
hr = manifestApp.GetStringValue("EntryPoint", out var tmpEntryPoint);
EntryPoint = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpEntryPoint);
Package = package ?? throw new ArgumentNullException(nameof(package));
DisplayName = ResourceFromPri(package.FullName, DisplayName);
Description = ResourceFromPri(package.FullName, Description);
logoUri = LogoUriFromManifest(manifestApp);
Enabled = true;
CanRunElevated = IfApplicationcanRunElevated();
}
private bool IfApplicationcanRunElevated()
{
if (EntryPoint == "Windows.FullTrustApplication")
{
return true;
}
else
{
var manifest = Package.Location + "\\AppxManifest.xml";
if (File.Exists(manifest))
{
var file = File.ReadAllText(manifest);
if (file.Contains("TrustLevel=\"mediumIL\"", StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
return false;
}
internal string ResourceFromPri(string packageFullName, string resourceReference)
{
const string prefix = "ms-resource:";
if (!string.IsNullOrWhiteSpace(resourceReference) && resourceReference.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
// magic comes from @talynone
// https://github.com/talynone/Wox.Plugin.WindowsUniversalAppLauncher/blob/master/StoreAppLauncher/Helpers/NativeApiHelper.cs#L139-L153
string key = resourceReference.Substring(prefix.Length);
string parsed;
if (key.StartsWith("//", StringComparison.Ordinal))
{
parsed = prefix + key;
}
else if (key.StartsWith("/", StringComparison.Ordinal))
{
parsed = prefix + "//" + key;
}
else if (key.Contains("resources", StringComparison.OrdinalIgnoreCase))
{
parsed = prefix + key;
}
else
{
parsed = prefix + "///resources/" + key;
}
var outBuffer = new StringBuilder(128);
string source = $"@{{{packageFullName}? {parsed}}}";
var capacity = (uint)outBuffer.Capacity;
var hResult = NativeMethods.SHLoadIndirectString(source, outBuffer, capacity, IntPtr.Zero);
if (hResult == Hresult.Ok)
{
var loaded = outBuffer.ToString();
if (!string.IsNullOrEmpty(loaded))
{
return loaded;
}
else
{
ProgramLogger.LogException(
$"|UWP|ResourceFromPri|{Package.Location}|Can't load null or empty result "
+ $"pri {source} in uwp location {Package.Location}", new NullReferenceException());
return string.Empty;
}
}
else
{
// https://github.com/Wox-launcher/Wox/issues/964
// known hresult 2147942522:
// 'Microsoft Corporation' violates pattern constraint of '\bms-resource:.{1,256}'.
// for
// Microsoft.MicrosoftOfficeHub_17.7608.23501.0_x64__8wekyb3d8bbwe: ms-resource://Microsoft.MicrosoftOfficeHub/officehubintl/AppManifest_GetOffice_Description
// Microsoft.BingFoodAndDrink_3.0.4.336_x64__8wekyb3d8bbwe: ms-resource:AppDescription
var e = Marshal.GetExceptionForHR((int)hResult);
ProgramLogger.LogException($"|UWP|ResourceFromPri|{Package.Location}|Load pri failed {source} with HResult {hResult} and location {Package.Location}", e);
return string.Empty;
}
}
else
{
return resourceReference;
}
}
internal string LogoUriFromManifest(IAppxManifestApplication app)
{
var logoKeyFromVersion = new Dictionary<PackageVersion, string>
{
{ PackageVersion.Windows10, "Square44x44Logo" },
{ PackageVersion.Windows81, "Square30x30Logo" },
{ PackageVersion.Windows8, "SmallLogo" },
};
if (logoKeyFromVersion.ContainsKey(Package.Version))
{
var key = logoKeyFromVersion[Package.Version];
var hr = app.GetStringValue(key, out var logoUri);
_ = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, logoUri);
return logoUri;
}
else
{
return string.Empty;
}
}
public void UpdatePath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
LogoPath = LogoPathFromUri(logoUri, "contrast-white");
}
else
{
LogoPath = LogoPathFromUri(logoUri, "contrast-black");
}
}
internal string LogoPathFromUri(string uri, string theme)
{
// all https://msdn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets
// windows 10 https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx
// windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size
// windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx
string path;
if (uri.Contains("\\", StringComparison.Ordinal))
{
path = Path.Combine(Package.Location, uri);
}
else
{
// for C:\Windows\MiracastView etc
path = Path.Combine(Package.Location, "Assets", uri);
}
var extension = Path.GetExtension(path);
if (extension != null)
{
var end = path.Length - extension.Length;
var prefix = path.Substring(0, end);
var paths = new List<string> { path };
};
if (logoKeyFromVersion.ContainsKey(Package.Version))
{
var key = logoKeyFromVersion[Package.Version];
var hr = app.GetStringValue(key, out var logoUri);
_ = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, logoUri);
return logoUri;
}
else
{
return string.Empty;
}
}
public void UpdatePath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
LogoPath = LogoPathFromUri(logoUri, "contrast-white");
}
else
{
LogoPath = LogoPathFromUri(logoUri, "contrast-black");
}
}
internal string LogoPathFromUri(string uri, string theme)
{
// all https://msdn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets
// windows 10 https://msdn.microsoft.com/en-us/library/windows/apps/dn934817.aspx
// windows 8.1 https://msdn.microsoft.com/en-us/library/windows/apps/hh965372.aspx#target_size
// windows 8 https://msdn.microsoft.com/en-us/library/windows/apps/br211475.aspx
string path;
if (uri.Contains("\\", StringComparison.Ordinal))
{
path = Path.Combine(Package.Location, uri);
}
else
{
// for C:\Windows\MiracastView etc
path = Path.Combine(Package.Location, "Assets", uri);
}
var extension = Path.GetExtension(path);
if (extension != null)
{
var end = path.Length - extension.Length;
var prefix = path.Substring(0, end);
var paths = new List<string> { path };
var scaleFactors = new Dictionary<PackageVersion, List<int>>
{
// scale factors on win10: https://docs.microsoft.com/en-us/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables,
{ PackageVersion.Windows10, new List<int> { 100, 125, 150, 200, 400 } },
{ PackageVersion.Windows81, new List<int> { 100, 120, 140, 160, 180 } },
{ PackageVersion.Windows8, new List<int> { 100 } },
};
if (scaleFactors.ContainsKey(Package.Version))
{
foreach (var factor in scaleFactors[Package.Version])
{
paths.Add($"{prefix}.scale-{factor}{extension}");
paths.Add($"{prefix}.scale-{factor}_{theme}{extension}");
paths.Add($"{prefix}.{theme}_scale-{factor}{extension}");
}
}
paths = paths.OrderByDescending(x => x.Contains(theme, StringComparison.OrdinalIgnoreCase)).ToList();
var selected = paths.FirstOrDefault(File.Exists);
if (!string.IsNullOrEmpty(selected))
{
return selected;
}
else
{
int appIconSize = 36;
var targetSizes = new List<int> { 16, 24, 30, 36, 44, 60, 72, 96, 128, 180, 256 }.AsParallel();
Dictionary<string, int> pathFactorPairs = new Dictionary<string, int>();
foreach (var factor in targetSizes)
{
string simplePath = $"{prefix}.targetsize-{factor}{extension}";
string suffixThemePath = $"{prefix}.targetsize-{factor}_{theme}{extension}";
string prefixThemePath = $"{prefix}.{theme}_targetsize-{factor}{extension}";
paths.Add(simplePath);
paths.Add(suffixThemePath);
paths.Add(prefixThemePath);
pathFactorPairs.Add(simplePath, factor);
pathFactorPairs.Add(suffixThemePath, factor);
pathFactorPairs.Add(prefixThemePath, factor);
}
paths = paths.OrderByDescending(x => x.Contains(theme, StringComparison.OrdinalIgnoreCase)).ToList();
var selectedIconPath = paths.OrderBy(x => Math.Abs(pathFactorPairs.GetValueOrDefault(x) - appIconSize)).FirstOrDefault(File.Exists);
if (!string.IsNullOrEmpty(selectedIconPath))
{
return selectedIconPath;
}
else
{
ProgramLogger.LogException(
$"|UWP|LogoPathFromUri|{Package.Location}" +
$"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException());
return string.Empty;
}
}
}
else
{
ProgramLogger.LogException(
$"|UWP|LogoPathFromUri|{Package.Location}" +
$"|Unable to find extension from {uri} for {UserModelId} " +
$"in package location {Package.Location}", new FileNotFoundException());
return string.Empty;
}
}
public ImageSource Logo()
{
var logo = ImageFromPath(LogoPath);
return logo;
}
private BitmapImage ImageFromPath(string path)
{
if (File.Exists(path))
{
MemoryStream memoryStream = new MemoryStream();
byte[] fileBytes = File.ReadAllBytes(path);
memoryStream.Write(fileBytes, 0, fileBytes.Length);
memoryStream.Position = 0;
var image = new BitmapImage();
image.BeginInit();
image.StreamSource = memoryStream;
image.EndInit();
return image;
}
else
{
ProgramLogger.LogException(
$"|UWP|ImageFromPath|{path}" +
$"|Unable to get logo for {UserModelId} from {path} and" +
$" located in {Package.Location}", new FileNotFoundException());
return new BitmapImage(new Uri(ImageLoader.ErrorIconPath));
}
}
public override string ToString()
{
return $"{DisplayName}: {Description}";
}
};
if (scaleFactors.ContainsKey(Package.Version))
{
foreach (var factor in scaleFactors[Package.Version])
{
paths.Add($"{prefix}.scale-{factor}{extension}");
paths.Add($"{prefix}.scale-{factor}_{theme}{extension}");
paths.Add($"{prefix}.{theme}_scale-{factor}{extension}");
}
}
paths = paths.OrderByDescending(x => x.Contains(theme, StringComparison.OrdinalIgnoreCase)).ToList();
var selected = paths.FirstOrDefault(File.Exists);
if (!string.IsNullOrEmpty(selected))
{
return selected;
}
else
{
int appIconSize = 36;
var targetSizes = new List<int> { 16, 24, 30, 36, 44, 60, 72, 96, 128, 180, 256 }.AsParallel();
Dictionary<string, int> pathFactorPairs = new Dictionary<string, int>();
foreach (var factor in targetSizes)
{
string simplePath = $"{prefix}.targetsize-{factor}{extension}";
string suffixThemePath = $"{prefix}.targetsize-{factor}_{theme}{extension}";
string prefixThemePath = $"{prefix}.{theme}_targetsize-{factor}{extension}";
paths.Add(simplePath);
paths.Add(suffixThemePath);
paths.Add(prefixThemePath);
pathFactorPairs.Add(simplePath, factor);
pathFactorPairs.Add(suffixThemePath, factor);
pathFactorPairs.Add(prefixThemePath, factor);
}
paths = paths.OrderByDescending(x => x.Contains(theme, StringComparison.OrdinalIgnoreCase)).ToList();
var selectedIconPath = paths.OrderBy(x => Math.Abs(pathFactorPairs.GetValueOrDefault(x) - appIconSize)).FirstOrDefault(File.Exists);
if (!string.IsNullOrEmpty(selectedIconPath))
{
return selectedIconPath;
}
else
{
ProgramLogger.LogException(
$"|UWP|LogoPathFromUri|{Package.Location}" +
$"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException());
return string.Empty;
}
}
}
else
{
ProgramLogger.LogException(
$"|UWP|LogoPathFromUri|{Package.Location}" +
$"|Unable to find extension from {uri} for {UserModelId} " +
$"in package location {Package.Location}", new FileNotFoundException());
return string.Empty;
}
}
public ImageSource Logo()
{
var logo = ImageFromPath(LogoPath);
return logo;
}
private BitmapImage ImageFromPath(string path)
{
if (File.Exists(path))
{
MemoryStream memoryStream = new MemoryStream();
byte[] fileBytes = File.ReadAllBytes(path);
memoryStream.Write(fileBytes, 0, fileBytes.Length);
memoryStream.Position = 0;
var image = new BitmapImage();
image.BeginInit();
image.StreamSource = memoryStream;
image.EndInit();
return image;
}
else
{
ProgramLogger.LogException(
$"|UWP|ImageFromPath|{path}" +
$"|Unable to get logo for {UserModelId} from {path} and" +
$" located in {Package.Location}", new FileNotFoundException());
return new BitmapImage(new Uri(ImageLoader.ErrorIconPath));
}
}
public override string ToString()
{
return $"{DisplayName}: {Description}";
}
}
}
}

View File

@@ -1,15 +1,15 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.Plugin.Program.Storage
{
internal interface IProgramRepository
{
void IndexPrograms();
void Load();
void Save();
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.Plugin.Program.Storage
{
internal interface IProgramRepository
{
void IndexPrograms();
void Load();
void Save();
}
}

View File

@@ -1,96 +1,96 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Plugin.Program.Logger;
using Microsoft.Plugin.Program.Programs;
using Windows.ApplicationModel;
using Wox.Infrastructure.Storage;
namespace Microsoft.Plugin.Program.Storage
{
/// <summary>
/// A repository for storing packaged applications such as UWP apps or appx packaged desktop apps.
/// This repository will also monitor for changes to the PackageCatelog and update the repository accordingly
/// </summary>
internal class PackageRepository : ListRepository<UWPApplication>, IProgramRepository
{
private IStorage<IList<UWPApplication>> _storage;
private IPackageCatalog _packageCatalog;
public PackageRepository(IPackageCatalog packageCatalog, IStorage<IList<UWPApplication>> storage)
{
_storage = storage ?? throw new ArgumentNullException(nameof(storage), "StorageRepository requires an initialized storage interface");
_packageCatalog = packageCatalog ?? throw new ArgumentNullException(nameof(packageCatalog), "PackageRepository expects an interface to be able to subscribe to package events");
_packageCatalog.PackageInstalling += OnPackageInstalling;
_packageCatalog.PackageUninstalling += OnPackageUninstalling;
}
public void OnPackageInstalling(PackageCatalog p, PackageInstallingEventArgs args)
{
if (args.IsComplete)
{
try
{
var packageWrapper = PackageWrapper.GetWrapperFromPackage(args.Package);
if (!string.IsNullOrEmpty(packageWrapper.InstalledLocation))
{
var uwp = new UWP(packageWrapper);
uwp.InitializeAppInfo(packageWrapper.InstalledLocation);
foreach (var app in uwp.Apps)
{
Add(app);
}
}
}
// InitializeAppInfo will throw if there is no AppxManifest.xml for the package.
// Note there are sometimes multiple packages per product and this doesn't necessarily mean that we haven't found the app.
// eg. "Could not find file 'C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminalPreview_2020.616.45.0_neutral_~_8wekyb3d8bbwe\\AppxManifest.xml'."
catch (System.IO.FileNotFoundException e)
{
ProgramLogger.LogException($"|UWP|OnPackageInstalling|{args.Package.InstalledLocation}|{e.Message}", e);
}
}
}
public void OnPackageUninstalling(PackageCatalog p, PackageUninstallingEventArgs args)
{
if (args.Progress == 0)
{
// find apps associated with this package.
var packageWrapper = PackageWrapper.GetWrapperFromPackage(args.Package);
var uwp = new UWP(packageWrapper);
var apps = Items.Where(a => a.Package.Equals(uwp)).ToArray();
foreach (var app in apps)
{
Remove(app);
}
}
}
public void IndexPrograms()
{
var windows10 = new Version(10, 0);
var support = Environment.OSVersion.Version.Major >= windows10.Major;
var applications = support ? Programs.UWP.All() : Array.Empty<UWPApplication>();
Set(applications);
}
public void Save()
{
_storage.Save(Items);
}
public void Load()
{
var items = _storage.TryLoad(Array.Empty<UWPApplication>());
Set(items);
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Plugin.Program.Logger;
using Microsoft.Plugin.Program.Programs;
using Windows.ApplicationModel;
using Wox.Infrastructure.Storage;
namespace Microsoft.Plugin.Program.Storage
{
/// <summary>
/// A repository for storing packaged applications such as UWP apps or appx packaged desktop apps.
/// This repository will also monitor for changes to the PackageCatelog and update the repository accordingly
/// </summary>
internal class PackageRepository : ListRepository<UWPApplication>, IProgramRepository
{
private IStorage<IList<UWPApplication>> _storage;
private IPackageCatalog _packageCatalog;
public PackageRepository(IPackageCatalog packageCatalog, IStorage<IList<UWPApplication>> storage)
{
_storage = storage ?? throw new ArgumentNullException(nameof(storage), "StorageRepository requires an initialized storage interface");
_packageCatalog = packageCatalog ?? throw new ArgumentNullException(nameof(packageCatalog), "PackageRepository expects an interface to be able to subscribe to package events");
_packageCatalog.PackageInstalling += OnPackageInstalling;
_packageCatalog.PackageUninstalling += OnPackageUninstalling;
}
public void OnPackageInstalling(PackageCatalog p, PackageInstallingEventArgs args)
{
if (args.IsComplete)
{
try
{
var packageWrapper = PackageWrapper.GetWrapperFromPackage(args.Package);
if (!string.IsNullOrEmpty(packageWrapper.InstalledLocation))
{
var uwp = new UWP(packageWrapper);
uwp.InitializeAppInfo(packageWrapper.InstalledLocation);
foreach (var app in uwp.Apps)
{
Add(app);
}
}
}
// InitializeAppInfo will throw if there is no AppxManifest.xml for the package.
// Note there are sometimes multiple packages per product and this doesn't necessarily mean that we haven't found the app.
// eg. "Could not find file 'C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminalPreview_2020.616.45.0_neutral_~_8wekyb3d8bbwe\\AppxManifest.xml'."
catch (System.IO.FileNotFoundException e)
{
ProgramLogger.LogException($"|UWP|OnPackageInstalling|{args.Package.InstalledLocation}|{e.Message}", e);
}
}
}
public void OnPackageUninstalling(PackageCatalog p, PackageUninstallingEventArgs args)
{
if (args.Progress == 0)
{
// find apps associated with this package.
var packageWrapper = PackageWrapper.GetWrapperFromPackage(args.Package);
var uwp = new UWP(packageWrapper);
var apps = Items.Where(a => a.Package.Equals(uwp)).ToArray();
foreach (var app in apps)
{
Remove(app);
}
}
}
public void IndexPrograms()
{
var windows10 = new Version(10, 0);
var support = Environment.OSVersion.Version.Major >= windows10.Major;
var applications = support ? Programs.UWP.All() : Array.Empty<UWPApplication>();
Set(applications);
}
public void Save()
{
_storage.Save(Items);
}
public void Load()
{
var items = _storage.TryLoad(Array.Empty<UWPApplication>());
Set(items);
}
}
}

View File

@@ -1,16 +1,16 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Globalization;
using System.IO;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.Storage;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.Storage;
using Win32Program = Microsoft.Plugin.Program.Programs.Win32Program;
namespace Microsoft.Plugin.Program.Storage
{
internal class Win32ProgramRepository : ListRepository<Programs.Win32Program>, IProgramRepository
@@ -60,9 +60,9 @@ namespace Microsoft.Plugin.Program.Storage
// Enable it to search in sub folders as well
_fileSystemWatcherHelpers[index].IncludeSubdirectories = true;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive>")]
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive>")]
private void OnAppRenamed(object sender, RenamedEventArgs e)
{
string oldPath = e.OldFullPath;
@@ -95,7 +95,7 @@ namespace Microsoft.Plugin.Program.Storage
{
Log.Info($"|Win32ProgramRepository|OnAppRenamed-{extension}Program|{oldPath}|Unable to create program from {oldPath}| {ex.Message}");
}
// To remove the old app which has been renamed and to add the new application.
if (oldApp != null)
{
@@ -106,9 +106,9 @@ namespace Microsoft.Plugin.Program.Storage
{
Add(newApp);
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentionally keeping the process alive")]
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentionally keeping the process alive")]
private void OnAppDeleted(object sender, FileSystemEventArgs e)
{
string path = e.FullPath;
@@ -152,7 +152,7 @@ namespace Microsoft.Plugin.Program.Storage
return app;
}
}
return null;
}
@@ -167,7 +167,7 @@ namespace Microsoft.Plugin.Program.Storage
return app;
}
}
return null;
}

View File

@@ -1,10 +1,10 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -14,7 +14,7 @@ using System.Windows.Data;
using System.Windows.Input;
using Microsoft.Plugin.Program.Views.Commands;
using Wox.Plugin;
namespace Microsoft.Plugin.Program.Views
{
/// <summary>
@@ -217,11 +217,11 @@ namespace Microsoft.Plugin.Program.Views
ProgramSettingDisplayList.RemoveDisabledFromSettings();
}
if (selectedItems.IsReindexRequired())
{
ReIndexing();
}
if (selectedItems.IsReindexRequired())
{
ReIndexing();
}
programSourceView.SelectedItems.Clear();
programSourceView.Items.Refresh();
@@ -309,4 +309,4 @@ namespace Microsoft.Plugin.Program.Views
}
}
}
}
}

View File

@@ -1,21 +1,21 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using static Microsoft.Plugin.Program.Programs.UWP;
namespace Microsoft.Plugin.Program.Win32
{
internal class NativeMethods
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
internal static extern Hresult SHCreateStreamOnFileEx(string fileName, Stgm grfMode, uint attributes, bool create, IStream reserved, out IStream stream);
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
internal static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved);
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using static Microsoft.Plugin.Program.Programs.UWP;
namespace Microsoft.Plugin.Program.Win32
{
internal class NativeMethods
{
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
internal static extern Hresult SHCreateStreamOnFileEx(string fileName, Stgm grfMode, uint attributes, bool create, IStream reserved, out IStream stream);
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
internal static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved);
}
}

View File

@@ -1,321 +1,321 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Input;
using Microsoft.PowerToys.Settings.UI.Lib;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.Storage;
using Wox.Plugin;
using Wox.Plugin.SharedCommands;
using Control = System.Windows.Controls.Control;
namespace Microsoft.Plugin.Shell
{
public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu, ISavable
{
private readonly Settings _settings;
private readonly PluginJsonStorage<Settings> _storage;
private string IconPath { get; set; }
private PluginInitContext _context;
public Main()
{
_storage = new PluginJsonStorage<Settings>();
_settings = _storage.Load();
}
public void Save()
{
_storage.Save();
}
public List<Result> Query(Query query)
{
List<Result> results = new List<Result>();
string cmd = query.Search;
if (string.IsNullOrEmpty(cmd))
{
return ResultsFromlHistory();
}
else
{
var queryCmd = GetCurrentCmd(cmd);
results.Add(queryCmd);
var history = GetHistoryCmds(cmd, queryCmd);
results.AddRange(history);
try
{
List<Result> folderPluginResults = Folder.Main.GetFolderPluginResults(query);
results.AddRange(folderPluginResults);
}
catch (Exception e)
{
Log.Exception($"|Microsoft.Plugin.Shell.Main.Query|Exception when query for <{query}>", e);
}
return results;
}
}
private List<Result> GetHistoryCmds(string cmd, Result result)
{
IEnumerable<Result> history = _settings.Count.Where(o => o.Key.Contains(cmd))
.OrderByDescending(o => o.Value)
.Select(m =>
{
if (m.Key == cmd)
{
result.SubTitle = "Shell: " + string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value);
return null;
}
var ret = new Result
{
Title = m.Key,
SubTitle = "Shell: " + string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value),
IcoPath = IconPath,
Action = c =>
{
Execute(Process.Start, PrepareProcessStartInfo(m.Key));
return true;
},
};
return ret;
}).Where(o => o != null).Take(4);
return history.ToList();
}
private Result GetCurrentCmd(string cmd)
{
Result result = new Result
{
Title = cmd,
Score = 5000,
SubTitle = "Shell: " + _context.API.GetTranslation("wox_plugin_cmd_execute_through_shell"),
IcoPath = IconPath,
Action = c =>
{
Execute(Process.Start, PrepareProcessStartInfo(cmd));
return true;
},
};
return result;
}
private List<Result> ResultsFromlHistory()
{
IEnumerable<Result> history = _settings.Count.OrderByDescending(o => o.Value)
.Select(m => new Result
{
Title = m.Key,
SubTitle = "Shell: " + string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value),
IcoPath = IconPath,
Action = c =>
{
Execute(Process.Start, PrepareProcessStartInfo(m.Key));
return true;
},
}).Take(5);
return history.ToList();
}
private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false)
{
command = command.Trim();
command = Environment.ExpandEnvironmentVariables(command);
var workingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var runAsAdministratorArg = !runAsAdministrator && !_settings.RunAsAdministrator ? string.Empty : "runas";
ProcessStartInfo info;
if (_settings.Shell == Shell.Cmd)
{
var arguments = _settings.LeaveShellOpen ? $"/k \"{command}\"" : $"/c \"{command}\" & pause";
info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, arguments, runAsAdministratorArg);
}
else if (_settings.Shell == Shell.Powershell)
{
string arguments;
if (_settings.LeaveShellOpen)
{
arguments = $"-NoExit \"{command}\"";
}
else
{
arguments = $"\"{command} ; Read-Host -Prompt \\\"Press Enter to continue\\\"\"";
}
info = ShellCommand.SetProcessStartInfo("powershell.exe", workingDirectory, arguments, runAsAdministratorArg);
}
else if (_settings.Shell == Shell.RunCommand)
{
// Open explorer if the path is a file or directory
if (Directory.Exists(command) || File.Exists(command))
{
info = ShellCommand.SetProcessStartInfo("explorer.exe", arguments: command, verb: runAsAdministratorArg);
}
else
{
var parts = command.Split(new[] { ' ' }, 2);
if (parts.Length == 2)
{
var filename = parts[0];
if (ExistInPath(filename))
{
var arguments = parts[1];
info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsAdministratorArg);
}
else
{
info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg);
}
}
else
{
info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg);
}
}
}
else
{
throw new NotImplementedException();
}
info.UseShellExecute = true;
_settings.AddCmdHistory(command);
return info;
}
private void Execute(Func<ProcessStartInfo, Process> startProcess, ProcessStartInfo info)
{
try
{
startProcess(info);
}
catch (FileNotFoundException e)
{
var name = "Plugin: Shell";
var message = $"Command not found: {e.Message}";
_context.API.ShowMsg(name, message);
}
catch (Win32Exception e)
{
var name = "Plugin: Shell";
var message = $"Error running the command: {e.Message}";
_context.API.ShowMsg(name, message);
}
}
private bool ExistInPath(string filename)
{
if (File.Exists(filename))
{
return true;
}
else
{
var values = Environment.GetEnvironmentVariable("PATH");
if (values != null)
{
foreach (var path in values.Split(';'))
{
var path1 = Path.Combine(path, filename);
var path2 = Path.Combine(path, filename + ".exe");
if (File.Exists(path1) || File.Exists(path2))
{
return true;
}
}
return false;
}
else
{
return false;
}
}
}
public void Init(PluginInitContext context)
{
this._context = context;
_context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(_context.API.GetCurrentTheme());
}
// Todo : Update with theme based IconPath
private void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
IconPath = "Images/shell.light.png";
}
else
{
IconPath = "Images/shell.dark.png";
}
}
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
{
UpdateIconPath(newTheme);
}
public Control CreateSettingPanel()
{
return new CMDSetting(_settings);
}
public string GetTranslatedPluginTitle()
{
return _context.API.GetTranslation("wox_plugin_cmd_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return _context.API.GetTranslation("wox_plugin_cmd_plugin_description");
}
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
var resultlist = new List<ContextMenuResult>
{
new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_administrator"),
Glyph = "\xE7EF",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.Enter,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = c =>
{
Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, true));
return true;
},
},
};
return resultlist;
}
public void UpdateSettings(PowerLauncherSettings settings)
{
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows.Input;
using Microsoft.PowerToys.Settings.UI.Lib;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.Storage;
using Wox.Plugin;
using Wox.Plugin.SharedCommands;
using Control = System.Windows.Controls.Control;
namespace Microsoft.Plugin.Shell
{
public class Main : IPlugin, ISettingProvider, IPluginI18n, IContextMenu, ISavable
{
private readonly Settings _settings;
private readonly PluginJsonStorage<Settings> _storage;
private string IconPath { get; set; }
private PluginInitContext _context;
public Main()
{
_storage = new PluginJsonStorage<Settings>();
_settings = _storage.Load();
}
public void Save()
{
_storage.Save();
}
public List<Result> Query(Query query)
{
List<Result> results = new List<Result>();
string cmd = query.Search;
if (string.IsNullOrEmpty(cmd))
{
return ResultsFromlHistory();
}
else
{
var queryCmd = GetCurrentCmd(cmd);
results.Add(queryCmd);
var history = GetHistoryCmds(cmd, queryCmd);
results.AddRange(history);
try
{
List<Result> folderPluginResults = Folder.Main.GetFolderPluginResults(query);
results.AddRange(folderPluginResults);
}
catch (Exception e)
{
Log.Exception($"|Microsoft.Plugin.Shell.Main.Query|Exception when query for <{query}>", e);
}
return results;
}
}
private List<Result> GetHistoryCmds(string cmd, Result result)
{
IEnumerable<Result> history = _settings.Count.Where(o => o.Key.Contains(cmd))
.OrderByDescending(o => o.Value)
.Select(m =>
{
if (m.Key == cmd)
{
result.SubTitle = "Shell: " + string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value);
return null;
}
var ret = new Result
{
Title = m.Key,
SubTitle = "Shell: " + string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value),
IcoPath = IconPath,
Action = c =>
{
Execute(Process.Start, PrepareProcessStartInfo(m.Key));
return true;
},
};
return ret;
}).Where(o => o != null).Take(4);
return history.ToList();
}
private Result GetCurrentCmd(string cmd)
{
Result result = new Result
{
Title = cmd,
Score = 5000,
SubTitle = "Shell: " + _context.API.GetTranslation("wox_plugin_cmd_execute_through_shell"),
IcoPath = IconPath,
Action = c =>
{
Execute(Process.Start, PrepareProcessStartInfo(cmd));
return true;
},
};
return result;
}
private List<Result> ResultsFromlHistory()
{
IEnumerable<Result> history = _settings.Count.OrderByDescending(o => o.Value)
.Select(m => new Result
{
Title = m.Key,
SubTitle = "Shell: " + string.Format(_context.API.GetTranslation("wox_plugin_cmd_cmd_has_been_executed_times"), m.Value),
IcoPath = IconPath,
Action = c =>
{
Execute(Process.Start, PrepareProcessStartInfo(m.Key));
return true;
},
}).Take(5);
return history.ToList();
}
private ProcessStartInfo PrepareProcessStartInfo(string command, bool runAsAdministrator = false)
{
command = command.Trim();
command = Environment.ExpandEnvironmentVariables(command);
var workingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var runAsAdministratorArg = !runAsAdministrator && !_settings.RunAsAdministrator ? string.Empty : "runas";
ProcessStartInfo info;
if (_settings.Shell == Shell.Cmd)
{
var arguments = _settings.LeaveShellOpen ? $"/k \"{command}\"" : $"/c \"{command}\" & pause";
info = ShellCommand.SetProcessStartInfo("cmd.exe", workingDirectory, arguments, runAsAdministratorArg);
}
else if (_settings.Shell == Shell.Powershell)
{
string arguments;
if (_settings.LeaveShellOpen)
{
arguments = $"-NoExit \"{command}\"";
}
else
{
arguments = $"\"{command} ; Read-Host -Prompt \\\"Press Enter to continue\\\"\"";
}
info = ShellCommand.SetProcessStartInfo("powershell.exe", workingDirectory, arguments, runAsAdministratorArg);
}
else if (_settings.Shell == Shell.RunCommand)
{
// Open explorer if the path is a file or directory
if (Directory.Exists(command) || File.Exists(command))
{
info = ShellCommand.SetProcessStartInfo("explorer.exe", arguments: command, verb: runAsAdministratorArg);
}
else
{
var parts = command.Split(new[] { ' ' }, 2);
if (parts.Length == 2)
{
var filename = parts[0];
if (ExistInPath(filename))
{
var arguments = parts[1];
info = ShellCommand.SetProcessStartInfo(filename, workingDirectory, arguments, runAsAdministratorArg);
}
else
{
info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg);
}
}
else
{
info = ShellCommand.SetProcessStartInfo(command, verb: runAsAdministratorArg);
}
}
}
else
{
throw new NotImplementedException();
}
info.UseShellExecute = true;
_settings.AddCmdHistory(command);
return info;
}
private void Execute(Func<ProcessStartInfo, Process> startProcess, ProcessStartInfo info)
{
try
{
startProcess(info);
}
catch (FileNotFoundException e)
{
var name = "Plugin: Shell";
var message = $"Command not found: {e.Message}";
_context.API.ShowMsg(name, message);
}
catch (Win32Exception e)
{
var name = "Plugin: Shell";
var message = $"Error running the command: {e.Message}";
_context.API.ShowMsg(name, message);
}
}
private bool ExistInPath(string filename)
{
if (File.Exists(filename))
{
return true;
}
else
{
var values = Environment.GetEnvironmentVariable("PATH");
if (values != null)
{
foreach (var path in values.Split(';'))
{
var path1 = Path.Combine(path, filename);
var path2 = Path.Combine(path, filename + ".exe");
if (File.Exists(path1) || File.Exists(path2))
{
return true;
}
}
return false;
}
else
{
return false;
}
}
}
public void Init(PluginInitContext context)
{
this._context = context;
_context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(_context.API.GetCurrentTheme());
}
// Todo : Update with theme based IconPath
private void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
IconPath = "Images/shell.light.png";
}
else
{
IconPath = "Images/shell.dark.png";
}
}
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
{
UpdateIconPath(newTheme);
}
public Control CreateSettingPanel()
{
return new CMDSetting(_settings);
}
public string GetTranslatedPluginTitle()
{
return _context.API.GetTranslation("wox_plugin_cmd_plugin_name");
}
public string GetTranslatedPluginDescription()
{
return _context.API.GetTranslation("wox_plugin_cmd_plugin_description");
}
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
{
var resultlist = new List<ContextMenuResult>
{
new ContextMenuResult
{
PluginName = Assembly.GetExecutingAssembly().GetName().Name,
Title = _context.API.GetTranslation("wox_plugin_cmd_run_as_administrator"),
Glyph = "\xE7EF",
FontFamily = "Segoe MDL2 Assets",
AcceleratorKey = Key.Enter,
AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift,
Action = c =>
{
Execute(Process.Start, PrepareProcessStartInfo(selectedResult.Title, true));
return true;
},
},
};
return resultlist;
}
public void UpdateSettings(PowerLauncherSettings settings)
{
}
}
}

View File

@@ -1,124 +1,124 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Class housing fuzzy matching methods
/// </summary>
public class FuzzyMatching
{
/// <summary>
/// Finds the best match (the one with the most
/// number of letters adjacent to each other) and
/// returns the index location of each of the letters
/// of the matches
/// </summary>
/// <param name="text">The text to search inside of</param>
/// <param name="searchText">the text to search for</param>
/// <returns>returns the index location of each of the letters of the matches</returns>
public static List<int> FindBestFuzzyMatch(string text, string searchText)
{
searchText = searchText.ToLower();
text = text.ToLower();
// Create a grid to march matches like
// eg.
// a b c a d e c f g
// a x x
// c x x
bool[,] matches = new bool[text.Length, searchText.Length];
for (int firstIndex = 0; firstIndex < text.Length; firstIndex++)
{
for (int secondIndex = 0; secondIndex < searchText.Length; secondIndex++)
{
matches[firstIndex, secondIndex] =
searchText[secondIndex] == text[firstIndex] ?
true :
false;
}
}
// use this table to get all the possible matches
List<List<int>> allMatches = GetAllMatchIndexes(matches);
// return the score that is the max
int maxScore = allMatches.Count > 0 ? CalculateScoreForMatches(allMatches[0]) : 0;
List<int> bestMatch = allMatches.Count > 0 ? allMatches[0] : new List<int>();
foreach (var match in allMatches)
{
int score = CalculateScoreForMatches(match);
if (score > maxScore)
{
bestMatch = match;
maxScore = score;
}
}
return bestMatch;
}
/// <summary>
/// Gets all the possible matches to the search string with in the text
/// </summary>
/// <param name="matches"> a table showing the matches as generated by
/// a two dimensional array with the first dimension the text and the second
/// one the search string and each cell marked as an intersection between the two</param>
/// <returns>a list of the possible combinations that match the search text</returns>
public static List<List<int>> GetAllMatchIndexes(bool[,] matches)
{
List<List<int>> results = new List<List<int>>();
for (int secondIndex = 0; secondIndex < matches.GetLength(1); secondIndex++)
{
for (int firstIndex = 0; firstIndex < matches.GetLength(0); firstIndex++)
{
if (secondIndex == 0 && matches[firstIndex, secondIndex])
{
results.Add(new List<int> { firstIndex });
}
else if (matches[firstIndex, secondIndex])
{
var tempList = results.Where(x => x.Count == secondIndex && x[x.Count - 1] < firstIndex).Select(x => x.ToList()).ToList();
foreach (var pathSofar in tempList)
{
pathSofar.Add(firstIndex);
}
results.AddRange(tempList);
}
}
results = results.Where(x => x.Count == secondIndex + 1).ToList();
}
return results.Where(x => x.Count == matches.GetLength(1)).ToList();
}
/// <summary>
/// Calculates the score for a string
/// </summary>
/// <param name="matches">the index of the matches</param>
/// <returns>an integer representing the score</returns>
public static int CalculateScoreForMatches(List<int> matches)
{
var score = 0;
for (int currentIndex = 1; currentIndex < matches.Count; currentIndex++)
{
var previousIndex = currentIndex - 1;
score -= matches[currentIndex] - matches[previousIndex];
}
return score == 0 ? -10000 : score;
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Class housing fuzzy matching methods
/// </summary>
public class FuzzyMatching
{
/// <summary>
/// Finds the best match (the one with the most
/// number of letters adjacent to each other) and
/// returns the index location of each of the letters
/// of the matches
/// </summary>
/// <param name="text">The text to search inside of</param>
/// <param name="searchText">the text to search for</param>
/// <returns>returns the index location of each of the letters of the matches</returns>
public static List<int> FindBestFuzzyMatch(string text, string searchText)
{
searchText = searchText.ToLower();
text = text.ToLower();
// Create a grid to march matches like
// eg.
// a b c a d e c f g
// a x x
// c x x
bool[,] matches = new bool[text.Length, searchText.Length];
for (int firstIndex = 0; firstIndex < text.Length; firstIndex++)
{
for (int secondIndex = 0; secondIndex < searchText.Length; secondIndex++)
{
matches[firstIndex, secondIndex] =
searchText[secondIndex] == text[firstIndex] ?
true :
false;
}
}
// use this table to get all the possible matches
List<List<int>> allMatches = GetAllMatchIndexes(matches);
// return the score that is the max
int maxScore = allMatches.Count > 0 ? CalculateScoreForMatches(allMatches[0]) : 0;
List<int> bestMatch = allMatches.Count > 0 ? allMatches[0] : new List<int>();
foreach (var match in allMatches)
{
int score = CalculateScoreForMatches(match);
if (score > maxScore)
{
bestMatch = match;
maxScore = score;
}
}
return bestMatch;
}
/// <summary>
/// Gets all the possible matches to the search string with in the text
/// </summary>
/// <param name="matches"> a table showing the matches as generated by
/// a two dimensional array with the first dimension the text and the second
/// one the search string and each cell marked as an intersection between the two</param>
/// <returns>a list of the possible combinations that match the search text</returns>
public static List<List<int>> GetAllMatchIndexes(bool[,] matches)
{
List<List<int>> results = new List<List<int>>();
for (int secondIndex = 0; secondIndex < matches.GetLength(1); secondIndex++)
{
for (int firstIndex = 0; firstIndex < matches.GetLength(0); firstIndex++)
{
if (secondIndex == 0 && matches[firstIndex, secondIndex])
{
results.Add(new List<int> { firstIndex });
}
else if (matches[firstIndex, secondIndex])
{
var tempList = results.Where(x => x.Count == secondIndex && x[x.Count - 1] < firstIndex).Select(x => x.ToList()).ToList();
foreach (var pathSofar in tempList)
{
pathSofar.Add(firstIndex);
}
results.AddRange(tempList);
}
}
results = results.Where(x => x.Count == secondIndex + 1).ToList();
}
return results.Where(x => x.Count == matches.GetLength(1)).ToList();
}
/// <summary>
/// Calculates the score for a string
/// </summary>
/// <param name="matches">the index of the matches</param>
/// <returns>an integer representing the score</returns>
public static int CalculateScoreForMatches(List<int> matches)
{
var score = 0;
for (int currentIndex = 1; currentIndex < matches.Count; currentIndex++)
{
var previousIndex = currentIndex - 1;
score -= matches[currentIndex] - matches[previousIndex];
}
return score == 0 ? -10000 : score;
}
}
}

View File

@@ -1,58 +1,58 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
using System;
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Class containing methods to control the live preview
/// </summary>
internal class LivePreview
{
/// <summary>
/// Makes sure that a window is excluded from the live preview
/// </summary>
/// <param name="hwnd">handle to the window to exclude</param>
public static void SetWindowExclusionFromLivePreview(IntPtr hwnd)
{
int renderPolicy = (int)InteropAndHelpers.DwmNCRenderingPolicy.Enabled;
InteropAndHelpers.DwmSetWindowAttribute(
hwnd,
12,
ref renderPolicy,
sizeof(int));
}
/// <summary>
/// Activates the live preview
/// </summary>
/// <param name="targetWindow">the window to show by making all other windows transparent</param>
/// <param name="windowToSpare">the window which should not be transparent but is not the target window</param>
public static void ActivateLivePreview(IntPtr targetWindow, IntPtr windowToSpare)
{
InteropAndHelpers.DwmpActivateLivePreview(
true,
targetWindow,
windowToSpare,
InteropAndHelpers.LivePreviewTrigger.Superbar,
IntPtr.Zero);
}
/// <summary>
/// Deactivates the live preview
/// </summary>
public static void DeactivateLivePreview()
{
InteropAndHelpers.DwmpActivateLivePreview(
false,
IntPtr.Zero,
IntPtr.Zero,
InteropAndHelpers.LivePreviewTrigger.AltTab,
IntPtr.Zero);
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
using System;
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Class containing methods to control the live preview
/// </summary>
internal class LivePreview
{
/// <summary>
/// Makes sure that a window is excluded from the live preview
/// </summary>
/// <param name="hwnd">handle to the window to exclude</param>
public static void SetWindowExclusionFromLivePreview(IntPtr hwnd)
{
int renderPolicy = (int)InteropAndHelpers.DwmNCRenderingPolicy.Enabled;
InteropAndHelpers.DwmSetWindowAttribute(
hwnd,
12,
ref renderPolicy,
sizeof(int));
}
/// <summary>
/// Activates the live preview
/// </summary>
/// <param name="targetWindow">the window to show by making all other windows transparent</param>
/// <param name="windowToSpare">the window which should not be transparent but is not the target window</param>
public static void ActivateLivePreview(IntPtr targetWindow, IntPtr windowToSpare)
{
InteropAndHelpers.DwmpActivateLivePreview(
true,
targetWindow,
windowToSpare,
InteropAndHelpers.LivePreviewTrigger.Superbar,
IntPtr.Zero);
}
/// <summary>
/// Deactivates the live preview
/// </summary>
public static void DeactivateLivePreview()
{
InteropAndHelpers.DwmpActivateLivePreview(
false,
IntPtr.Zero,
IntPtr.Zero,
InteropAndHelpers.LivePreviewTrigger.AltTab,
IntPtr.Zero);
}
}
}

View File

@@ -1,106 +1,106 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
using System;
using System.Collections.Generic;
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Class that represents the state of the desktops windows
/// </summary>
internal class OpenWindows
{
/// <summary>
/// Delegate handler for open windows updates
/// </summary>
public delegate void OpenWindowsUpdateHandler(object sender, SearchController.SearchResultUpdateEventArgs e);
/// <summary>
/// Event raised when there is an update to the list of open windows
/// </summary>
public event OpenWindowsUpdateHandler OnOpenWindowsUpdate
{
add { }
remove { }
}
/// <summary>
/// List of all the open windows
/// </summary>
private readonly List<Window> windows = new List<Window>();
/// <summary>
/// An instance of the class OpenWindows
/// </summary>
private static OpenWindows instance;
/// <summary>
/// Gets the list of all open windows
/// </summary>
public List<Window> Windows
{
get { return new List<Window>(windows); }
}
/// <summary>
/// Gets an instance property of this class that makes sure that
/// the first instance gets created and that all the requests
/// end up at that one instance
/// </summary>
public static OpenWindows Instance
{
get
{
if (instance == null)
{
instance = new OpenWindows();
}
return instance;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="OpenWindows"/> class.
/// Private constructor to make sure there is never
/// more than one instance of this class
/// </summary>
private OpenWindows()
{
}
/// <summary>
/// Updates the list of open windows
/// </summary>
public void UpdateOpenWindowsList()
{
windows.Clear();
InteropAndHelpers.CallBackPtr callbackptr = new InteropAndHelpers.CallBackPtr(WindowEnumerationCallBack);
InteropAndHelpers.EnumWindows(callbackptr, 0);
}
/// <summary>
/// Call back method for window enumeration
/// </summary>
/// <param name="hwnd">The handle to the current window being enumerated</param>
/// <param name="lParam">Value being passed from the caller (we don't use this but might come in handy
/// in the future</param>
/// <returns>true to make sure to continue enumeration</returns>
public bool WindowEnumerationCallBack(IntPtr hwnd, IntPtr lParam)
{
Window newWindow = new Window(hwnd);
if (newWindow.IsWindow && newWindow.Visible && newWindow.IsOwner &&
(!newWindow.IsToolWindow || newWindow.IsAppWindow) && !newWindow.TaskListDeleted &&
newWindow.ClassName != "Windows.UI.Core.CoreWindow")
{
windows.Add(newWindow);
}
return true;
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
using System;
using System.Collections.Generic;
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Class that represents the state of the desktops windows
/// </summary>
internal class OpenWindows
{
/// <summary>
/// Delegate handler for open windows updates
/// </summary>
public delegate void OpenWindowsUpdateHandler(object sender, SearchController.SearchResultUpdateEventArgs e);
/// <summary>
/// Event raised when there is an update to the list of open windows
/// </summary>
public event OpenWindowsUpdateHandler OnOpenWindowsUpdate
{
add { }
remove { }
}
/// <summary>
/// List of all the open windows
/// </summary>
private readonly List<Window> windows = new List<Window>();
/// <summary>
/// An instance of the class OpenWindows
/// </summary>
private static OpenWindows instance;
/// <summary>
/// Gets the list of all open windows
/// </summary>
public List<Window> Windows
{
get { return new List<Window>(windows); }
}
/// <summary>
/// Gets an instance property of this class that makes sure that
/// the first instance gets created and that all the requests
/// end up at that one instance
/// </summary>
public static OpenWindows Instance
{
get
{
if (instance == null)
{
instance = new OpenWindows();
}
return instance;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="OpenWindows"/> class.
/// Private constructor to make sure there is never
/// more than one instance of this class
/// </summary>
private OpenWindows()
{
}
/// <summary>
/// Updates the list of open windows
/// </summary>
public void UpdateOpenWindowsList()
{
windows.Clear();
InteropAndHelpers.CallBackPtr callbackptr = new InteropAndHelpers.CallBackPtr(WindowEnumerationCallBack);
InteropAndHelpers.EnumWindows(callbackptr, 0);
}
/// <summary>
/// Call back method for window enumeration
/// </summary>
/// <param name="hwnd">The handle to the current window being enumerated</param>
/// <param name="lParam">Value being passed from the caller (we don't use this but might come in handy
/// in the future</param>
/// <returns>true to make sure to continue enumeration</returns>
public bool WindowEnumerationCallBack(IntPtr hwnd, IntPtr lParam)
{
Window newWindow = new Window(hwnd);
if (newWindow.IsWindow && newWindow.Visible && newWindow.IsOwner &&
(!newWindow.IsToolWindow || newWindow.IsAppWindow) && !newWindow.TaskListDeleted &&
newWindow.ClassName != "Windows.UI.Core.CoreWindow")
{
windows.Add(newWindow);
}
return true;
}
}
}

View File

@@ -1,186 +1,186 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Responsible for searching and finding matches for the strings provided.
/// Essentially the UI independent model of the application
/// </summary>
internal class SearchController
{
/// <summary>
/// the current search text
/// </summary>
private string searchText;
/// <summary>
/// Open window search results
/// </summary
private List<SearchResult> searchMatches;
/// <summary>
/// Singleton pattern
/// </summary>
private static SearchController instance;
/// <summary>
/// Delegate handler for open windows updates
/// </summary>
public delegate void SearchResultUpdateHandler(object sender, SearchResultUpdateEventArgs e);
/// <summary>
/// Event raised when there is an update to the list of open windows
/// </summary>
public event SearchResultUpdateHandler OnSearchResultUpdate;
/// <summary>
/// Gets or sets the current search text
/// </summary>
public string SearchText
{
get
{
return searchText;
}
set
{
searchText = value.ToLower().Trim();
}
}
/// <summary>
/// Gets the open window search results
/// </summary>
public List<SearchResult> SearchMatches
{
get { return new List<SearchResult>(searchMatches).OrderByDescending(x => x.Score).ToList(); }
}
/// <summary>
/// Gets singleton Pattern
/// </summary>
public static SearchController Instance
{
get
{
if (instance == null)
{
instance = new SearchController();
}
return instance;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SearchController"/> class.
/// Initializes the search controller object
/// </summary>
private SearchController()
{
searchText = string.Empty;
OpenWindows.Instance.OnOpenWindowsUpdate += OpenWindowsUpdateHandler;
}
/// <summary>
/// Event handler for when the search text has been updated
/// </summary>
public async Task UpdateSearchText(string searchText)
{
SearchText = searchText;
await SyncOpenWindowsWithModelAsync();
}
/// <summary>
/// Event handler called when the OpenWindows list changes
/// </summary>
/// <param name="sender">sending item</param>
/// <param name="e">event arg</param>
public async void OpenWindowsUpdateHandler(object sender, SearchResultUpdateEventArgs e)
{
await SyncOpenWindowsWithModelAsync();
}
/// <summary>
/// Syncs the open windows with the OpenWindows Model
/// </summary>
public async Task SyncOpenWindowsWithModelAsync()
{
System.Diagnostics.Debug.Print("Syncing WindowSearch result with OpenWindows Model");
List<Window> snapshotOfOpenWindows = OpenWindows.Instance.Windows;
if (SearchText == string.Empty)
{
searchMatches = new List<SearchResult>();
}
else
{
searchMatches = await FuzzySearchOpenWindowsAsync(snapshotOfOpenWindows);
}
OnSearchResultUpdate?.Invoke(this, new SearchResultUpdateEventArgs());
}
/// <summary>
/// Redirecting method for Fuzzy searching
/// </summary>
/// <param name="openWindows">what windows are open</param>
/// <returns>Returns search results</returns>
private Task<List<SearchResult>> FuzzySearchOpenWindowsAsync(List<Window> openWindows)
{
return Task.Run(
() =>
FuzzySearchOpenWindows(openWindows));
}
/// <summary>
/// Search method that matches the title of windows with the user search text
/// </summary>
/// <param name="openWindows">what windows are open</param>
/// <returns>Returns search results</returns>
private List<SearchResult> FuzzySearchOpenWindows(List<Window> openWindows)
{
List<SearchResult> result = new List<SearchResult>();
List<SearchString> searchStrings = new List<SearchString>();
searchStrings.Add(new SearchString(searchText, SearchResult.SearchType.Fuzzy));
foreach (var searchString in searchStrings)
{
foreach (var window in openWindows)
{
var titleMatch = FuzzyMatching.FindBestFuzzyMatch(window.Title, searchString.SearchText);
var processMatch = FuzzyMatching.FindBestFuzzyMatch(window.ProcessName, searchString.SearchText);
if ((titleMatch.Count != 0 || processMatch.Count != 0) &&
window.Title.Length != 0)
{
var temp = new SearchResult(window, titleMatch, processMatch, searchString.SearchType);
result.Add(temp);
}
}
}
System.Diagnostics.Debug.Print("Found " + result.Count + " windows that match the search text");
return result;
}
/// <summary>
/// Event args for a window list update event
/// </summary>
public class SearchResultUpdateEventArgs : EventArgs
{
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Responsible for searching and finding matches for the strings provided.
/// Essentially the UI independent model of the application
/// </summary>
internal class SearchController
{
/// <summary>
/// the current search text
/// </summary>
private string searchText;
/// <summary>
/// Open window search results
/// </summary
private List<SearchResult> searchMatches;
/// <summary>
/// Singleton pattern
/// </summary>
private static SearchController instance;
/// <summary>
/// Delegate handler for open windows updates
/// </summary>
public delegate void SearchResultUpdateHandler(object sender, SearchResultUpdateEventArgs e);
/// <summary>
/// Event raised when there is an update to the list of open windows
/// </summary>
public event SearchResultUpdateHandler OnSearchResultUpdate;
/// <summary>
/// Gets or sets the current search text
/// </summary>
public string SearchText
{
get
{
return searchText;
}
set
{
searchText = value.ToLower().Trim();
}
}
/// <summary>
/// Gets the open window search results
/// </summary>
public List<SearchResult> SearchMatches
{
get { return new List<SearchResult>(searchMatches).OrderByDescending(x => x.Score).ToList(); }
}
/// <summary>
/// Gets singleton Pattern
/// </summary>
public static SearchController Instance
{
get
{
if (instance == null)
{
instance = new SearchController();
}
return instance;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SearchController"/> class.
/// Initializes the search controller object
/// </summary>
private SearchController()
{
searchText = string.Empty;
OpenWindows.Instance.OnOpenWindowsUpdate += OpenWindowsUpdateHandler;
}
/// <summary>
/// Event handler for when the search text has been updated
/// </summary>
public async Task UpdateSearchText(string searchText)
{
SearchText = searchText;
await SyncOpenWindowsWithModelAsync();
}
/// <summary>
/// Event handler called when the OpenWindows list changes
/// </summary>
/// <param name="sender">sending item</param>
/// <param name="e">event arg</param>
public async void OpenWindowsUpdateHandler(object sender, SearchResultUpdateEventArgs e)
{
await SyncOpenWindowsWithModelAsync();
}
/// <summary>
/// Syncs the open windows with the OpenWindows Model
/// </summary>
public async Task SyncOpenWindowsWithModelAsync()
{
System.Diagnostics.Debug.Print("Syncing WindowSearch result with OpenWindows Model");
List<Window> snapshotOfOpenWindows = OpenWindows.Instance.Windows;
if (SearchText == string.Empty)
{
searchMatches = new List<SearchResult>();
}
else
{
searchMatches = await FuzzySearchOpenWindowsAsync(snapshotOfOpenWindows);
}
OnSearchResultUpdate?.Invoke(this, new SearchResultUpdateEventArgs());
}
/// <summary>
/// Redirecting method for Fuzzy searching
/// </summary>
/// <param name="openWindows">what windows are open</param>
/// <returns>Returns search results</returns>
private Task<List<SearchResult>> FuzzySearchOpenWindowsAsync(List<Window> openWindows)
{
return Task.Run(
() =>
FuzzySearchOpenWindows(openWindows));
}
/// <summary>
/// Search method that matches the title of windows with the user search text
/// </summary>
/// <param name="openWindows">what windows are open</param>
/// <returns>Returns search results</returns>
private List<SearchResult> FuzzySearchOpenWindows(List<Window> openWindows)
{
List<SearchResult> result = new List<SearchResult>();
List<SearchString> searchStrings = new List<SearchString>();
searchStrings.Add(new SearchString(searchText, SearchResult.SearchType.Fuzzy));
foreach (var searchString in searchStrings)
{
foreach (var window in openWindows)
{
var titleMatch = FuzzyMatching.FindBestFuzzyMatch(window.Title, searchString.SearchText);
var processMatch = FuzzyMatching.FindBestFuzzyMatch(window.ProcessName, searchString.SearchText);
if ((titleMatch.Count != 0 || processMatch.Count != 0) &&
window.Title.Length != 0)
{
var temp = new SearchResult(window, titleMatch, processMatch, searchString.SearchType);
result.Add(temp);
}
}
}
System.Diagnostics.Debug.Print("Found " + result.Count + " windows that match the search text");
return result;
}
/// <summary>
/// Event args for a window list update event
/// </summary>
public class SearchResultUpdateEventArgs : EventArgs
{
}
}
}

View File

@@ -1,136 +1,136 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
using System.Collections.Generic;
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Contains search result windows with each window including the reason why the result was included
/// </summary>
public class SearchResult
{
/// <summary>
/// Gets the actual window reference for the search result
/// </summary>
public Window Result
{
get;
private set;
}
/// <summary>
/// Gets the list of indexes of the matching characters for the search in the title window
/// </summary>
public List<int> SearchMatchesInTitle
{
get;
private set;
}
/// <summary>
/// Gets the list of indexes of the matching characters for the search in the
/// name of the process
/// </summary>
public List<int> SearchMatchesInProcessName
{
get;
private set;
}
/// <summary>
/// Gets the type of match (shortcut, fuzzy or nothing)
/// </summary>
public SearchType SearchResultMatchType
{
get;
private set;
}
/// <summary>
/// Gets a score indicating how well this matches what we are looking for
/// </summary>
public int Score
{
get;
private set;
}
/// <summary>
/// Gets the source of where the best score was found
/// </summary>
public TextType BestScoreSource
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the <see cref="SearchResult"/> class.
/// Constructor
/// </summary>
public SearchResult(Window window, List<int> matchesInTitle, List<int> matchesInProcessName, SearchType matchType)
{
Result = window;
SearchMatchesInTitle = matchesInTitle;
SearchMatchesInProcessName = matchesInProcessName;
SearchResultMatchType = matchType;
CalculateScore();
}
/// <summary>
/// Calculates the score for how closely this window matches the search string
/// </summary>
/// <remarks>
/// Higher Score is better
/// </remarks>
private void CalculateScore()
{
if (FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName) >
FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle))
{
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName);
BestScoreSource = TextType.ProcessName;
}
else
{
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle);
BestScoreSource = TextType.WindowTitle;
}
}
/// <summary>
/// The type of text that a string represents
/// </summary>
public enum TextType
{
ProcessName,
WindowTitle,
}
/// <summary>
/// The type of search
/// </summary>
public enum SearchType
{
/// <summary>
/// the search string is empty, which means all open windows are
/// going to be returned
/// </summary>
Empty,
/// <summary>
/// Regular fuzzy match search
/// </summary>
Fuzzy,
/// <summary>
/// The user has entered text that has been matched to a shortcut
/// and the shortcut is now being searched
/// </summary>
Shortcut,
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
using System.Collections.Generic;
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Contains search result windows with each window including the reason why the result was included
/// </summary>
public class SearchResult
{
/// <summary>
/// Gets the actual window reference for the search result
/// </summary>
public Window Result
{
get;
private set;
}
/// <summary>
/// Gets the list of indexes of the matching characters for the search in the title window
/// </summary>
public List<int> SearchMatchesInTitle
{
get;
private set;
}
/// <summary>
/// Gets the list of indexes of the matching characters for the search in the
/// name of the process
/// </summary>
public List<int> SearchMatchesInProcessName
{
get;
private set;
}
/// <summary>
/// Gets the type of match (shortcut, fuzzy or nothing)
/// </summary>
public SearchType SearchResultMatchType
{
get;
private set;
}
/// <summary>
/// Gets a score indicating how well this matches what we are looking for
/// </summary>
public int Score
{
get;
private set;
}
/// <summary>
/// Gets the source of where the best score was found
/// </summary>
public TextType BestScoreSource
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the <see cref="SearchResult"/> class.
/// Constructor
/// </summary>
public SearchResult(Window window, List<int> matchesInTitle, List<int> matchesInProcessName, SearchType matchType)
{
Result = window;
SearchMatchesInTitle = matchesInTitle;
SearchMatchesInProcessName = matchesInProcessName;
SearchResultMatchType = matchType;
CalculateScore();
}
/// <summary>
/// Calculates the score for how closely this window matches the search string
/// </summary>
/// <remarks>
/// Higher Score is better
/// </remarks>
private void CalculateScore()
{
if (FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName) >
FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle))
{
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInProcessName);
BestScoreSource = TextType.ProcessName;
}
else
{
Score = FuzzyMatching.CalculateScoreForMatches(SearchMatchesInTitle);
BestScoreSource = TextType.WindowTitle;
}
}
/// <summary>
/// The type of text that a string represents
/// </summary>
public enum TextType
{
ProcessName,
WindowTitle,
}
/// <summary>
/// The type of search
/// </summary>
public enum SearchType
{
/// <summary>
/// the search string is empty, which means all open windows are
/// going to be returned
/// </summary>
Empty,
/// <summary>
/// Regular fuzzy match search
/// </summary>
Fuzzy,
/// <summary>
/// The user has entered text that has been matched to a shortcut
/// and the shortcut is now being searched
/// </summary>
Shortcut,
}
}
}

View File

@@ -1,46 +1,46 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// A class to represent a search string
/// </summary>
/// <remarks>Class was added inorder to be able to attach various context data to
/// a search string</remarks>
internal class SearchString
{
/// <summary>
/// Gets where is the search string coming from (is it a shortcut
/// or direct string, etc...)
/// </summary>
public SearchResult.SearchType SearchType
{
get;
private set;
}
/// <summary>
/// Gets the actual text we are searching for
/// </summary>
public string SearchText
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the <see cref="SearchString"/> class.
/// Constructor
/// </summary>
/// <param name="searchText">text from search</param>
/// <param name="searchType">type of search</param>
public SearchString(string searchText, SearchResult.SearchType searchType)
{
SearchText = searchText;
SearchType = searchType;
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// A class to represent a search string
/// </summary>
/// <remarks>Class was added inorder to be able to attach various context data to
/// a search string</remarks>
internal class SearchString
{
/// <summary>
/// Gets where is the search string coming from (is it a shortcut
/// or direct string, etc...)
/// </summary>
public SearchResult.SearchType SearchType
{
get;
private set;
}
/// <summary>
/// Gets the actual text we are searching for
/// </summary>
public string SearchText
{
get;
private set;
}
/// <summary>
/// Initializes a new instance of the <see cref="SearchString"/> class.
/// Constructor
/// </summary>
/// <param name="searchText">text from search</param>
/// <param name="searchType">type of search</param>
public SearchString(string searchText, SearchResult.SearchType searchType)
{
SearchText = searchText;
SearchType = searchType;
}
}
}

View File

@@ -1,402 +1,402 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Represents a specific open window
/// </summary>
public class Window
{
/// <summary>
/// Maximum size of a file name
/// </summary>
private const int MaximumFileNameLength = 1000;
/// <summary>
/// The list of owners of a window so that we don't have to
/// constantly query for the process owning a specific window
/// </summary>
private static readonly Dictionary<IntPtr, string> _handlesToProcessCache = new Dictionary<IntPtr, string>();
/// <summary>
/// The list of icons from process so that we don't have to keep
/// loading them from disk
/// </summary>
private static readonly Dictionary<uint, ImageSource> _processIdsToIconsCache = new Dictionary<uint, ImageSource>();
/// <summary>
/// The handle to the window
/// </summary>
private readonly IntPtr hwnd;
/// <summary>
/// Gets the title of the window (the string displayed at the top of the window)
/// </summary>
public string Title
{
get
{
int sizeOfTitle = InteropAndHelpers.GetWindowTextLength(hwnd);
if (sizeOfTitle++ > 0)
{
StringBuilder titleBuffer = new StringBuilder(sizeOfTitle);
InteropAndHelpers.GetWindowText(hwnd, titleBuffer, sizeOfTitle);
return titleBuffer.ToString();
}
else
{
return string.Empty;
}
}
}
/// <summary>
/// Gets the handle to the window
/// </summary>
public IntPtr Hwnd
{
get { return hwnd; }
}
public uint ProcessID { get; set; }
/// <summary>
/// Gets returns the name of the process
/// </summary>
public string ProcessName
{
get
{
lock (_handlesToProcessCache)
{
if (_handlesToProcessCache.Count > 7000)
{
Debug.Print("Clearing Process Cache because it's size is " + _handlesToProcessCache.Count);
_handlesToProcessCache.Clear();
}
if (!_handlesToProcessCache.ContainsKey(Hwnd))
{
var processName = GetProcessNameFromWindowHandle(Hwnd);
if (processName.Length != 0)
{
_handlesToProcessCache.Add(
Hwnd,
processName.ToString().Split('\\').Reverse().ToArray()[0]);
}
else
{
_handlesToProcessCache.Add(Hwnd, string.Empty);
}
}
if (_handlesToProcessCache[hwnd].ToLower() == "applicationframehost.exe")
{
new Task(() =>
{
InteropAndHelpers.CallBackPtr callbackptr = new InteropAndHelpers.CallBackPtr((IntPtr hwnd, IntPtr lParam) =>
{
var childProcessId = GetProcessIDFromWindowHandle(hwnd);
if (childProcessId != ProcessID)
{
_handlesToProcessCache[Hwnd] = GetProcessNameFromWindowHandle(hwnd);
return false;
}
else
{
return true;
}
});
InteropAndHelpers.EnumChildWindows(Hwnd, callbackptr, 0);
}).Start();
}
return _handlesToProcessCache[hwnd];
}
}
}
/// <summary>
/// Gets returns the name of the class for the window represented
/// </summary>
public string ClassName
{
get
{
StringBuilder windowClassName = new StringBuilder(300);
InteropAndHelpers.GetClassName(Hwnd, windowClassName, windowClassName.MaxCapacity);
return windowClassName.ToString();
}
}
/// <summary>
/// Gets represents the Window Icon for the specified window
/// </summary>
public ImageSource WindowIcon
{
get
{
lock (_processIdsToIconsCache)
{
InteropAndHelpers.GetWindowThreadProcessId(Hwnd, out uint processId);
if (!_processIdsToIconsCache.ContainsKey(processId))
{
try
{
Process process = Process.GetProcessById((int)processId);
Icon tempIcon = Icon.ExtractAssociatedIcon(process.Modules[0].FileName);
_processIdsToIconsCache.Add(processId, Imaging.CreateBitmapSourceFromHIcon(
tempIcon.Handle,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions()));
}
catch
{
BitmapImage failedImage = new BitmapImage(new Uri(@"Images\failedIcon.jpg", UriKind.Relative));
_processIdsToIconsCache.Add(processId, failedImage);
}
}
return _processIdsToIconsCache[processId];
}
}
}
/// <summary>
/// Gets a value indicating whether is the window visible (might return false if it is a hidden IE tab)
/// </summary>
public bool Visible
{
get
{
return InteropAndHelpers.IsWindowVisible(Hwnd);
}
}
/// <summary>
/// Gets a value indicating whether determines whether the specified window handle identifies an existing window.
/// </summary>
public bool IsWindow
{
get
{
return InteropAndHelpers.IsWindow(Hwnd);
}
}
/// <summary>
/// Gets a value indicating whether get a value indicating whether is the window GWL_EX_STYLE is a toolwindow
/// </summary>
public bool IsToolWindow
{
get
{
return (InteropAndHelpers.GetWindowLong(Hwnd, InteropAndHelpers.GWL_EXSTYLE) &
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_TOOLWINDOW) ==
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_TOOLWINDOW;
}
}
/// <summary>
/// Gets a value indicating whether get a value indicating whether the window GWL_EX_STYLE is an appwindow
/// </summary>
public bool IsAppWindow
{
get
{
return (InteropAndHelpers.GetWindowLong(Hwnd, InteropAndHelpers.GWL_EXSTYLE) &
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_APPWINDOW) ==
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_APPWINDOW;
}
}
/// <summary>
/// Gets a value indicating whether get a value indicating whether the window has ITaskList_Deleted property
/// </summary>
public bool TaskListDeleted
{
get
{
return InteropAndHelpers.GetProp(Hwnd, "ITaskList_Deleted") != IntPtr.Zero;
}
}
/// <summary>
/// Gets a value indicating whether get a value indicating whether the app is a cloaked UWP app
/// </summary>
public bool IsUWPCloaked
{
get
{
return IsWindowCloaked() && ClassName == "ApplicationFrameWindow";
}
}
/// <summary>
/// Gets a value indicating whether determines whether the specified windows is the owner
/// </summary>
public bool IsOwner
{
get
{
return InteropAndHelpers.GetWindow(Hwnd, InteropAndHelpers.GetWindowCmd.GW_OWNER) != null;
}
}
/// <summary>
/// Gets a value indicating whether is the window cloaked. To detect UWP apps in background or win32 apps running in another virtual desktop
/// </summary>
public bool IsWindowCloaked()
{
int isCloaked = 0;
const int DWMWA_CLOAKED = 14;
InteropAndHelpers.DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, out isCloaked, sizeof(int));
return isCloaked != 0;
}
/// <summary>
/// Gets a value indicating whether returns true if the window is minimized
/// </summary>
public bool Minimized
{
get
{
return GetWindowSizeState() == WindowSizeState.Minimized;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="Window"/> class.
/// Initializes a new Window representation
/// </summary>
/// <param name="hwnd">the handle to the window we are representing</param>
public Window(IntPtr hwnd)
{
// TODO: Add verification as to whether the window handle is valid
this.hwnd = hwnd;
}
/// <summary>
/// Highlights a window to help the user identify the window that has been selected
/// </summary>
public void HighlightWindow()
{
throw new NotImplementedException();
}
/// <summary>
/// Switches desktop focus to the window
/// </summary>
public void SwitchToWindow()
{
// The following block is necessary because
// 1) There is a weird flashing behavior when trying
// to use ShowWindow for switching tabs in IE
// 2) SetForegroundWindow fails on minimized windows
if (ProcessName.ToLower().Equals("iexplore.exe") || !Minimized)
{
InteropAndHelpers.SetForegroundWindow(Hwnd);
}
else
{
InteropAndHelpers.ShowWindow(Hwnd, InteropAndHelpers.ShowWindowCommands.Restore);
}
InteropAndHelpers.FlashWindow(Hwnd, true);
}
/// <summary>
/// Converts the window name to string along with the process name
/// </summary>
/// <returns>The title of the window</returns>
public override string ToString()
{
return Title + " (" + ProcessName.ToUpper() + ")";
}
/// <summary>
/// Returns what the window size is
/// </summary>
/// <returns>The state (minimized, maximized, etc..) of the window</returns>
public WindowSizeState GetWindowSizeState()
{
InteropAndHelpers.GetWindowPlacement(Hwnd, out InteropAndHelpers.WINDOWPLACEMENT placement);
switch (placement.ShowCmd)
{
case InteropAndHelpers.ShowWindowCommands.Normal:
return WindowSizeState.Normal;
case InteropAndHelpers.ShowWindowCommands.Minimize:
case InteropAndHelpers.ShowWindowCommands.ShowMinimized:
return WindowSizeState.Minimized;
case InteropAndHelpers.ShowWindowCommands.Maximize: // No need for ShowMaximized here since its also of value 3
return WindowSizeState.Maximized;
default:
// throw new Exception("Don't know how to handle window state = " + placement.ShowCmd);
return WindowSizeState.Unknown;
}
}
/// <summary>
/// Enum to simplify the state of the window
/// </summary>
public enum WindowSizeState
{
Normal,
Minimized,
Maximized,
Unknown,
}
/// <summary>
/// Gets the name of the process using the window handle
/// </summary>
/// <param name="hwnd">The handle to the window</param>
/// <returns>A string representing the process name or an empty string if the function fails</returns>
private string GetProcessNameFromWindowHandle(IntPtr hwnd)
{
uint processId = GetProcessIDFromWindowHandle(hwnd);
ProcessID = processId;
IntPtr processHandle = InteropAndHelpers.OpenProcess(InteropAndHelpers.ProcessAccessFlags.AllAccess, true, (int)processId);
StringBuilder processName = new StringBuilder(MaximumFileNameLength);
if (InteropAndHelpers.GetProcessImageFileName(processHandle, processName, MaximumFileNameLength) != 0)
{
return processName.ToString().Split('\\').Reverse().ToArray()[0];
}
else
{
return string.Empty;
}
}
/// <summary>
/// Gets the process ID for the Window handle
/// </summary>
/// <param name="hwnd">The handle to the window</param>
/// <returns>The process ID</returns>
private uint GetProcessIDFromWindowHandle(IntPtr hwnd)
{
InteropAndHelpers.GetWindowThreadProcessId(hwnd, out uint processId);
return processId;
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Code forked from Betsegaw Tadele's https://github.com/betsegaw/windowwalker/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace Microsoft.Plugin.WindowWalker.Components
{
/// <summary>
/// Represents a specific open window
/// </summary>
public class Window
{
/// <summary>
/// Maximum size of a file name
/// </summary>
private const int MaximumFileNameLength = 1000;
/// <summary>
/// The list of owners of a window so that we don't have to
/// constantly query for the process owning a specific window
/// </summary>
private static readonly Dictionary<IntPtr, string> _handlesToProcessCache = new Dictionary<IntPtr, string>();
/// <summary>
/// The list of icons from process so that we don't have to keep
/// loading them from disk
/// </summary>
private static readonly Dictionary<uint, ImageSource> _processIdsToIconsCache = new Dictionary<uint, ImageSource>();
/// <summary>
/// The handle to the window
/// </summary>
private readonly IntPtr hwnd;
/// <summary>
/// Gets the title of the window (the string displayed at the top of the window)
/// </summary>
public string Title
{
get
{
int sizeOfTitle = InteropAndHelpers.GetWindowTextLength(hwnd);
if (sizeOfTitle++ > 0)
{
StringBuilder titleBuffer = new StringBuilder(sizeOfTitle);
InteropAndHelpers.GetWindowText(hwnd, titleBuffer, sizeOfTitle);
return titleBuffer.ToString();
}
else
{
return string.Empty;
}
}
}
/// <summary>
/// Gets the handle to the window
/// </summary>
public IntPtr Hwnd
{
get { return hwnd; }
}
public uint ProcessID { get; set; }
/// <summary>
/// Gets returns the name of the process
/// </summary>
public string ProcessName
{
get
{
lock (_handlesToProcessCache)
{
if (_handlesToProcessCache.Count > 7000)
{
Debug.Print("Clearing Process Cache because it's size is " + _handlesToProcessCache.Count);
_handlesToProcessCache.Clear();
}
if (!_handlesToProcessCache.ContainsKey(Hwnd))
{
var processName = GetProcessNameFromWindowHandle(Hwnd);
if (processName.Length != 0)
{
_handlesToProcessCache.Add(
Hwnd,
processName.ToString().Split('\\').Reverse().ToArray()[0]);
}
else
{
_handlesToProcessCache.Add(Hwnd, string.Empty);
}
}
if (_handlesToProcessCache[hwnd].ToLower() == "applicationframehost.exe")
{
new Task(() =>
{
InteropAndHelpers.CallBackPtr callbackptr = new InteropAndHelpers.CallBackPtr((IntPtr hwnd, IntPtr lParam) =>
{
var childProcessId = GetProcessIDFromWindowHandle(hwnd);
if (childProcessId != ProcessID)
{
_handlesToProcessCache[Hwnd] = GetProcessNameFromWindowHandle(hwnd);
return false;
}
else
{
return true;
}
});
InteropAndHelpers.EnumChildWindows(Hwnd, callbackptr, 0);
}).Start();
}
return _handlesToProcessCache[hwnd];
}
}
}
/// <summary>
/// Gets returns the name of the class for the window represented
/// </summary>
public string ClassName
{
get
{
StringBuilder windowClassName = new StringBuilder(300);
InteropAndHelpers.GetClassName(Hwnd, windowClassName, windowClassName.MaxCapacity);
return windowClassName.ToString();
}
}
/// <summary>
/// Gets represents the Window Icon for the specified window
/// </summary>
public ImageSource WindowIcon
{
get
{
lock (_processIdsToIconsCache)
{
InteropAndHelpers.GetWindowThreadProcessId(Hwnd, out uint processId);
if (!_processIdsToIconsCache.ContainsKey(processId))
{
try
{
Process process = Process.GetProcessById((int)processId);
Icon tempIcon = Icon.ExtractAssociatedIcon(process.Modules[0].FileName);
_processIdsToIconsCache.Add(processId, Imaging.CreateBitmapSourceFromHIcon(
tempIcon.Handle,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions()));
}
catch
{
BitmapImage failedImage = new BitmapImage(new Uri(@"Images\failedIcon.jpg", UriKind.Relative));
_processIdsToIconsCache.Add(processId, failedImage);
}
}
return _processIdsToIconsCache[processId];
}
}
}
/// <summary>
/// Gets a value indicating whether is the window visible (might return false if it is a hidden IE tab)
/// </summary>
public bool Visible
{
get
{
return InteropAndHelpers.IsWindowVisible(Hwnd);
}
}
/// <summary>
/// Gets a value indicating whether determines whether the specified window handle identifies an existing window.
/// </summary>
public bool IsWindow
{
get
{
return InteropAndHelpers.IsWindow(Hwnd);
}
}
/// <summary>
/// Gets a value indicating whether get a value indicating whether is the window GWL_EX_STYLE is a toolwindow
/// </summary>
public bool IsToolWindow
{
get
{
return (InteropAndHelpers.GetWindowLong(Hwnd, InteropAndHelpers.GWL_EXSTYLE) &
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_TOOLWINDOW) ==
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_TOOLWINDOW;
}
}
/// <summary>
/// Gets a value indicating whether get a value indicating whether the window GWL_EX_STYLE is an appwindow
/// </summary>
public bool IsAppWindow
{
get
{
return (InteropAndHelpers.GetWindowLong(Hwnd, InteropAndHelpers.GWL_EXSTYLE) &
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_APPWINDOW) ==
(uint)InteropAndHelpers.ExtendedWindowStyles.WS_EX_APPWINDOW;
}
}
/// <summary>
/// Gets a value indicating whether get a value indicating whether the window has ITaskList_Deleted property
/// </summary>
public bool TaskListDeleted
{
get
{
return InteropAndHelpers.GetProp(Hwnd, "ITaskList_Deleted") != IntPtr.Zero;
}
}
/// <summary>
/// Gets a value indicating whether get a value indicating whether the app is a cloaked UWP app
/// </summary>
public bool IsUWPCloaked
{
get
{
return IsWindowCloaked() && ClassName == "ApplicationFrameWindow";
}
}
/// <summary>
/// Gets a value indicating whether determines whether the specified windows is the owner
/// </summary>
public bool IsOwner
{
get
{
return InteropAndHelpers.GetWindow(Hwnd, InteropAndHelpers.GetWindowCmd.GW_OWNER) != null;
}
}
/// <summary>
/// Gets a value indicating whether is the window cloaked. To detect UWP apps in background or win32 apps running in another virtual desktop
/// </summary>
public bool IsWindowCloaked()
{
int isCloaked = 0;
const int DWMWA_CLOAKED = 14;
InteropAndHelpers.DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, out isCloaked, sizeof(int));
return isCloaked != 0;
}
/// <summary>
/// Gets a value indicating whether returns true if the window is minimized
/// </summary>
public bool Minimized
{
get
{
return GetWindowSizeState() == WindowSizeState.Minimized;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="Window"/> class.
/// Initializes a new Window representation
/// </summary>
/// <param name="hwnd">the handle to the window we are representing</param>
public Window(IntPtr hwnd)
{
// TODO: Add verification as to whether the window handle is valid
this.hwnd = hwnd;
}
/// <summary>
/// Highlights a window to help the user identify the window that has been selected
/// </summary>
public void HighlightWindow()
{
throw new NotImplementedException();
}
/// <summary>
/// Switches desktop focus to the window
/// </summary>
public void SwitchToWindow()
{
// The following block is necessary because
// 1) There is a weird flashing behavior when trying
// to use ShowWindow for switching tabs in IE
// 2) SetForegroundWindow fails on minimized windows
if (ProcessName.ToLower().Equals("iexplore.exe") || !Minimized)
{
InteropAndHelpers.SetForegroundWindow(Hwnd);
}
else
{
InteropAndHelpers.ShowWindow(Hwnd, InteropAndHelpers.ShowWindowCommands.Restore);
}
InteropAndHelpers.FlashWindow(Hwnd, true);
}
/// <summary>
/// Converts the window name to string along with the process name
/// </summary>
/// <returns>The title of the window</returns>
public override string ToString()
{
return Title + " (" + ProcessName.ToUpper() + ")";
}
/// <summary>
/// Returns what the window size is
/// </summary>
/// <returns>The state (minimized, maximized, etc..) of the window</returns>
public WindowSizeState GetWindowSizeState()
{
InteropAndHelpers.GetWindowPlacement(Hwnd, out InteropAndHelpers.WINDOWPLACEMENT placement);
switch (placement.ShowCmd)
{
case InteropAndHelpers.ShowWindowCommands.Normal:
return WindowSizeState.Normal;
case InteropAndHelpers.ShowWindowCommands.Minimize:
case InteropAndHelpers.ShowWindowCommands.ShowMinimized:
return WindowSizeState.Minimized;
case InteropAndHelpers.ShowWindowCommands.Maximize: // No need for ShowMaximized here since its also of value 3
return WindowSizeState.Maximized;
default:
// throw new Exception("Don't know how to handle window state = " + placement.ShowCmd);
return WindowSizeState.Unknown;
}
}
/// <summary>
/// Enum to simplify the state of the window
/// </summary>
public enum WindowSizeState
{
Normal,
Minimized,
Maximized,
Unknown,
}
/// <summary>
/// Gets the name of the process using the window handle
/// </summary>
/// <param name="hwnd">The handle to the window</param>
/// <returns>A string representing the process name or an empty string if the function fails</returns>
private string GetProcessNameFromWindowHandle(IntPtr hwnd)
{
uint processId = GetProcessIDFromWindowHandle(hwnd);
ProcessID = processId;
IntPtr processHandle = InteropAndHelpers.OpenProcess(InteropAndHelpers.ProcessAccessFlags.AllAccess, true, (int)processId);
StringBuilder processName = new StringBuilder(MaximumFileNameLength);
if (InteropAndHelpers.GetProcessImageFileName(processHandle, processName, MaximumFileNameLength) != 0)
{
return processName.ToString().Split('\\').Reverse().ToArray()[0];
}
else
{
return string.Empty;
}
}
/// <summary>
/// Gets the process ID for the Window handle
/// </summary>
/// <param name="hwnd">The handle to the window</param>
/// <returns>The process ID</returns>
private uint GetProcessIDFromWindowHandle(IntPtr hwnd)
{
InteropAndHelpers.GetWindowThreadProcessId(hwnd, out uint processId);
return processId;
}
}
}

View File

@@ -1,31 +1,31 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.Plugin.WindowWalker.Components
{
internal class WindowResult : Window
{
/// <summary>
/// Number of letters in between constant for when
/// the result hasn't been set yet
/// </summary>
public const int NoResult = -1;
/// <summary>
/// Gets or sets properties that signify how many characters (including spaces)
/// were found when matching the results
/// </summary>
public int LettersInBetweenScore { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="WindowResult"/> class.
/// Constructor for WindowResult
/// </summary>
public WindowResult(Window window)
: base(window.Hwnd)
{
LettersInBetweenScore = NoResult;
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
namespace Microsoft.Plugin.WindowWalker.Components
{
internal class WindowResult : Window
{
/// <summary>
/// Number of letters in between constant for when
/// the result hasn't been set yet
/// </summary>
public const int NoResult = -1;
/// <summary>
/// Gets or sets properties that signify how many characters (including spaces)
/// were found when matching the results
/// </summary>
public int LettersInBetweenScore { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="WindowResult"/> class.
/// Constructor for WindowResult
/// </summary>
public WindowResult(Window window)
: base(window.Hwnd)
{
LettersInBetweenScore = NoResult;
}
}
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.Plugin.WindowWalker.Components;
@@ -12,9 +12,9 @@ namespace Microsoft.Plugin.WindowWalker
public class Main : IPlugin, IPluginI18n
{
private static List<SearchResult> _results = new List<SearchResult>();
private string IconPath { get; set; }
private PluginInitContext Context { get; set; }
static Main()
@@ -24,45 +24,45 @@ namespace Microsoft.Plugin.WindowWalker
}
public List<Result> Query(Query query)
{
SearchController.Instance.UpdateSearchText(query.RawQuery).Wait();
{
SearchController.Instance.UpdateSearchText(query.RawQuery).Wait();
OpenWindows.Instance.UpdateOpenWindowsList();
return _results.Select(x => new Result()
{
Title = x.Result.Title,
IcoPath = IconPath,
SubTitle = "Running: " + x.Result.ProcessName,
Action = c =>
{
x.Result.SwitchToWindow();
return true;
Action = c =>
{
x.Result.SwitchToWindow();
return true;
},
}).ToList();
}
public void Init(PluginInitContext context)
{
Context = context;
Context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(Context.API.GetCurrentTheme());
}
// Todo : Update with theme based IconPath
private void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
IconPath = "Images/windowwalker.light.png";
}
else
{
IconPath = "Images/windowwalker.dark.png";
}
}
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
{
UpdateIconPath(newTheme);
Context = context;
Context.API.ThemeChanged += OnThemeChanged;
UpdateIconPath(Context.API.GetCurrentTheme());
}
// Todo : Update with theme based IconPath
private void UpdateIconPath(Theme theme)
{
if (theme == Theme.Light || theme == Theme.HighContrastWhite)
{
IconPath = "Images/windowwalker.light.png";
}
else
{
IconPath = "Images/windowwalker.dark.png";
}
}
private void OnThemeChanged(Theme currentTheme, Theme newTheme)
{
UpdateIconPath(newTheme);
}
public string GetTranslatedPluginTitle()
@@ -75,9 +75,9 @@ namespace Microsoft.Plugin.WindowWalker
return Context.API.GetTranslation("wox_plugin_windowwalker_plugin_description");
}
private static void SearchResultUpdated(object sender, SearchController.SearchResultUpdateEventArgs e)
{
_results = SearchController.Instance.SearchMatches;
private static void SearchResultUpdated(object sender, SearchController.SearchResultUpdateEventArgs e)
{
_results = SearchController.Instance.SearchMatches;
}
}
}

View File

@@ -1,18 +1,18 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerLauncher.Telemetry
{
[EventData]
public class LauncherBootEvent : EventBase, IEvent
{
public double BootTimeMs { get; set; }
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance;
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerLauncher.Telemetry
{
[EventData]
public class LauncherBootEvent : EventBase, IEvent
{
public double BootTimeMs { get; set; }
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance;
}
}

View File

@@ -1,16 +1,16 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerLauncher.Telemetry
{
[EventData]
public class LauncherFirstDeleteEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerLauncher.Telemetry
{
[EventData]
public class LauncherFirstDeleteEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@@ -1,16 +1,16 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerLauncher.Telemetry
{
[EventData]
public class LauncherHideEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerLauncher.Telemetry
{
[EventData]
public class LauncherHideEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@@ -1,25 +1,25 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerLauncher.Telemetry
{
/// <summary>
/// ETW Event for when the user initiates a query
/// </summary>
[EventData]
public class LauncherQueryEvent : EventBase, IEvent
{
public double QueryTimeMs { get; set; }
public int QueryLength { get; set; }
public int NumResults { get; set; }
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance;
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerLauncher.Telemetry
{
/// <summary>
/// ETW Event for when the user initiates a query
/// </summary>
[EventData]
public class LauncherQueryEvent : EventBase, IEvent
{
public double QueryTimeMs { get; set; }
public int QueryLength { get; set; }
public int NumResults { get; set; }
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance;
}
}

View File

@@ -1,31 +1,31 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerLauncher.Telemetry
{
/// <summary>
/// ETW event for when a result is actioned.
/// </summary>
[EventData]
public class LauncherResultActionEvent : EventBase, IEvent
{
public enum TriggerType
{
Click,
KeyboardShortcut,
}
public string Trigger { get; set; }
public string PluginName { get; set; }
public string ActionName { get; set; }
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerLauncher.Telemetry
{
/// <summary>
/// ETW event for when a result is actioned.
/// </summary>
[EventData]
public class LauncherResultActionEvent : EventBase, IEvent
{
public enum TriggerType
{
Click,
KeyboardShortcut,
}
public string Trigger { get; set; }
public string PluginName { get; set; }
public string ActionName { get; set; }
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@@ -1,16 +1,16 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerLauncher.Telemetry
{
[EventData]
public class LauncherShowEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Tracing;
using Microsoft.PowerToys.Telemetry;
using Microsoft.PowerToys.Telemetry.Events;
namespace Microsoft.PowerLauncher.Telemetry
{
[EventData]
public class LauncherShowEvent : EventBase, IEvent
{
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
}
}

View File

@@ -1,213 +1,213 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Windows;
using ManagedCommon;
using Microsoft.PowerLauncher.Telemetry;
using Microsoft.PowerToys.Telemetry;
using PowerLauncher.Helper;
using PowerLauncher.ViewModel;
using Wox;
using Wox.Core.Plugin;
using Wox.Core.Resource;
using Wox.Infrastructure;
using Wox.Infrastructure.Http;
using Wox.Infrastructure.Image;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.UserSettings;
using Wox.Plugin;
using Stopwatch = Wox.Infrastructure.Stopwatch;
namespace PowerLauncher
{
public partial class App : IDisposable, ISingleInstanceApp
{
public static PublicAPIInstance API { get; private set; }
private readonly Alphabet _alphabet = new Alphabet();
private const string Unique = "PowerLauncher_Unique_Application_Mutex";
private static bool _disposed = false;
private static int _powerToysPid;
private Settings _settings;
private MainViewModel _mainVM;
private MainWindow _mainWindow;
private ThemeManager _themeManager;
private SettingWindowViewModel _settingsVM;
private StringMatcher _stringMatcher;
private SettingsWatcher _settingsWatcher;
[STAThread]
public static void Main(string[] args)
{
if (SingleInstance<App>.InitializeAsFirstInstance(Unique))
{
if (args?.Length > 0)
{
_ = int.TryParse(args[0], out _powerToysPid);
}
using (var application = new App())
{
application.InitializeComponent();
application.Run();
}
}
}
private void OnStartup(object sender, StartupEventArgs e)
{
RunnerHelper.WaitForPowerToysRunner(_powerToysPid, () =>
{
try
{
Dispose();
}
finally
{
Environment.Exit(0);
}
});
var bootTime = new System.Diagnostics.Stopwatch();
bootTime.Start();
Stopwatch.Normal("|App.OnStartup|Startup cost", () =>
{
Log.Info("|App.OnStartup|Begin PowerToys Run startup ----------------------------------------------------");
Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}");
RegisterAppDomainExceptions();
RegisterDispatcherUnhandledException();
_themeManager = new ThemeManager(this);
ImageLoader.Initialize(_themeManager.GetCurrentTheme());
_settingsVM = new SettingWindowViewModel();
_settings = _settingsVM.Settings;
_alphabet.Initialize(_settings);
_stringMatcher = new StringMatcher(_alphabet);
StringMatcher.Instance = _stringMatcher;
_stringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision;
PluginManager.LoadPlugins(_settings.PluginSettings);
_mainVM = new MainViewModel(_settings);
_mainWindow = new MainWindow(_settings, _mainVM);
API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet, _themeManager);
PluginManager.InitializePlugins(API);
Current.MainWindow = _mainWindow;
Current.MainWindow.Title = Constant.ExeFileName;
// happlebao 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 settings
Http.Proxy = _settings.Proxy;
RegisterExitEvents();
_settingsWatcher = new SettingsWatcher(_settings);
_mainVM.MainWindowVisibility = Visibility.Visible;
_mainVM.ColdStartFix();
_themeManager.ThemeChanged += OnThemeChanged;
Log.Info("|App.OnStartup|End PowerToys Run startup ---------------------------------------------------- ");
bootTime.Stop();
PowerToysTelemetry.Log.WriteEvent(new LauncherBootEvent() { BootTimeMs = bootTime.ElapsedMilliseconds });
// [Conditional("RELEASE")]
// check update every 5 hours
// check updates on startup
});
}
private void RegisterExitEvents()
{
AppDomain.CurrentDomain.ProcessExit += (s, e) => Dispose();
Current.Exit += (s, e) => Dispose();
Current.SessionEnding += (s, e) => Dispose();
}
/// <summary>
/// Callback when windows theme is changed.
/// </summary>
/// <param name="oldTheme">Previous Theme</param>
/// <param name="newTheme">Current Theme</param>
private void OnThemeChanged(Theme oldTheme, Theme newTheme)
{
ImageLoader.UpdateIconPath(newTheme);
_mainVM.Query();
}
/// <summary>
/// let exception throw as normal is better for Debug
/// </summary>
[Conditional("RELEASE")]
private void RegisterDispatcherUnhandledException()
{
DispatcherUnhandledException += ErrorReporting.DispatcherUnhandledException;
}
/// <summary>
/// let exception throw as normal is better for Debug
/// </summary>
[Conditional("RELEASE")]
private static void RegisterAppDomainExceptions()
{
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
}
public void OnSecondAppStarted()
{
Current.MainWindow.Visibility = Visibility.Visible;
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
Stopwatch.Normal("|App.OnExit|Exit cost", () =>
{
Log.Info("|App.OnExit| Start PowerToys Run Exit---------------------------------------------------- ");
if (disposing)
{
_themeManager.ThemeChanged -= OnThemeChanged;
API.SaveAppAllSettings();
PluginManager.Dispose();
_mainWindow.Dispose();
API.Dispose();
_mainVM.Dispose();
_themeManager.Dispose();
_disposed = true;
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
_disposed = true;
Log.Info("|App.OnExit| End PowerToys Run Exit ---------------------------------------------------- ");
});
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~App()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Diagnostics;
using System.Windows;
using ManagedCommon;
using Microsoft.PowerLauncher.Telemetry;
using Microsoft.PowerToys.Telemetry;
using PowerLauncher.Helper;
using PowerLauncher.ViewModel;
using Wox;
using Wox.Core.Plugin;
using Wox.Core.Resource;
using Wox.Infrastructure;
using Wox.Infrastructure.Http;
using Wox.Infrastructure.Image;
using Wox.Infrastructure.Logger;
using Wox.Infrastructure.UserSettings;
using Wox.Plugin;
using Stopwatch = Wox.Infrastructure.Stopwatch;
namespace PowerLauncher
{
public partial class App : IDisposable, ISingleInstanceApp
{
public static PublicAPIInstance API { get; private set; }
private readonly Alphabet _alphabet = new Alphabet();
private const string Unique = "PowerLauncher_Unique_Application_Mutex";
private static bool _disposed = false;
private static int _powerToysPid;
private Settings _settings;
private MainViewModel _mainVM;
private MainWindow _mainWindow;
private ThemeManager _themeManager;
private SettingWindowViewModel _settingsVM;
private StringMatcher _stringMatcher;
private SettingsWatcher _settingsWatcher;
[STAThread]
public static void Main(string[] args)
{
if (SingleInstance<App>.InitializeAsFirstInstance(Unique))
{
if (args?.Length > 0)
{
_ = int.TryParse(args[0], out _powerToysPid);
}
using (var application = new App())
{
application.InitializeComponent();
application.Run();
}
}
}
private void OnStartup(object sender, StartupEventArgs e)
{
RunnerHelper.WaitForPowerToysRunner(_powerToysPid, () =>
{
try
{
Dispose();
}
finally
{
Environment.Exit(0);
}
});
var bootTime = new System.Diagnostics.Stopwatch();
bootTime.Start();
Stopwatch.Normal("|App.OnStartup|Startup cost", () =>
{
Log.Info("|App.OnStartup|Begin PowerToys Run startup ----------------------------------------------------");
Log.Info($"|App.OnStartup|Runtime info:{ErrorReporting.RuntimeInfo()}");
RegisterAppDomainExceptions();
RegisterDispatcherUnhandledException();
_themeManager = new ThemeManager(this);
ImageLoader.Initialize(_themeManager.GetCurrentTheme());
_settingsVM = new SettingWindowViewModel();
_settings = _settingsVM.Settings;
_alphabet.Initialize(_settings);
_stringMatcher = new StringMatcher(_alphabet);
StringMatcher.Instance = _stringMatcher;
_stringMatcher.UserSettingSearchPrecision = _settings.QuerySearchPrecision;
PluginManager.LoadPlugins(_settings.PluginSettings);
_mainVM = new MainViewModel(_settings);
_mainWindow = new MainWindow(_settings, _mainVM);
API = new PublicAPIInstance(_settingsVM, _mainVM, _alphabet, _themeManager);
PluginManager.InitializePlugins(API);
Current.MainWindow = _mainWindow;
Current.MainWindow.Title = Constant.ExeFileName;
// happlebao 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 settings
Http.Proxy = _settings.Proxy;
RegisterExitEvents();
_settingsWatcher = new SettingsWatcher(_settings);
_mainVM.MainWindowVisibility = Visibility.Visible;
_mainVM.ColdStartFix();
_themeManager.ThemeChanged += OnThemeChanged;
Log.Info("|App.OnStartup|End PowerToys Run startup ---------------------------------------------------- ");
bootTime.Stop();
PowerToysTelemetry.Log.WriteEvent(new LauncherBootEvent() { BootTimeMs = bootTime.ElapsedMilliseconds });
// [Conditional("RELEASE")]
// check update every 5 hours
// check updates on startup
});
}
private void RegisterExitEvents()
{
AppDomain.CurrentDomain.ProcessExit += (s, e) => Dispose();
Current.Exit += (s, e) => Dispose();
Current.SessionEnding += (s, e) => Dispose();
}
/// <summary>
/// Callback when windows theme is changed.
/// </summary>
/// <param name="oldTheme">Previous Theme</param>
/// <param name="newTheme">Current Theme</param>
private void OnThemeChanged(Theme oldTheme, Theme newTheme)
{
ImageLoader.UpdateIconPath(newTheme);
_mainVM.Query();
}
/// <summary>
/// let exception throw as normal is better for Debug
/// </summary>
[Conditional("RELEASE")]
private void RegisterDispatcherUnhandledException()
{
DispatcherUnhandledException += ErrorReporting.DispatcherUnhandledException;
}
/// <summary>
/// let exception throw as normal is better for Debug
/// </summary>
[Conditional("RELEASE")]
private static void RegisterAppDomainExceptions()
{
AppDomain.CurrentDomain.UnhandledException += ErrorReporting.UnhandledExceptionHandle;
}
public void OnSecondAppStarted()
{
Current.MainWindow.Visibility = Visibility.Visible;
}
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
Stopwatch.Normal("|App.OnExit|Exit cost", () =>
{
Log.Info("|App.OnExit| Start PowerToys Run Exit---------------------------------------------------- ");
if (disposing)
{
_themeManager.ThemeChanged -= OnThemeChanged;
API.SaveAppAllSettings();
PluginManager.Dispose();
_mainWindow.Dispose();
API.Dispose();
_mainVM.Dispose();
_themeManager.Dispose();
_disposed = true;
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
_disposed = true;
Log.Info("|App.OnExit| End PowerToys Run Exit ---------------------------------------------------- ");
});
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~App()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -1,35 +1,35 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PowerLauncher.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("https://aka.ms/powerToys")]
public string GithubRepo {
get {
return ((string)(this["GithubRepo"]));
}
}
}
}
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace PowerLauncher.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
[global::System.Configuration.ApplicationScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("https://aka.ms/powerToys")]
public string GithubRepo {
get {
return ((string)(this["GithubRepo"]));
}
}
}
}

View File

@@ -1,277 +1,277 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using PowerLauncher.Helper;
using Wox.Infrastructure.UserSettings;
using Wox.Plugin;
namespace PowerLauncher.ViewModel
{
public class ResultsViewModel : BaseModel
{
private readonly object _collectionLock = new object();
private readonly Settings _settings;
public ResultsViewModel()
{
Results = new ResultCollection();
BindingOperations.EnableCollectionSynchronization(Results, _collectionLock);
}
public ResultsViewModel(Settings settings)
: this()
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_settings.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(_settings.MaxResultsToShow))
{
Application.Current.Dispatcher.Invoke(() =>
{
OnPropertyChanged(nameof(MaxHeight));
});
}
};
}
public int MaxHeight
{
get
{
return _settings.MaxResultsToShow * 75;
}
}
public int SelectedIndex { get; set; }
private ResultViewModel _selectedItem;
public ResultViewModel SelectedItem
{
get
{
return _selectedItem;
}
set
{
if (value != null)
{
if (_selectedItem != null)
{
_selectedItem.DeactivateContextButtons(ResultViewModel.ActivationType.Selection);
}
_selectedItem = value;
_selectedItem.ActivateContextButtons(ResultViewModel.ActivationType.Selection);
}
else
{
_selectedItem = value;
}
}
}
public Thickness Margin { get; set; }
public Visibility Visibility { get; set; } = Visibility.Hidden;
public ResultCollection Results { get; }
private static int InsertIndexOf(int newScore, IList<ResultViewModel> list)
{
int index = 0;
for (; index < list.Count; index++)
{
var result = list[index];
if (newScore > result.Result.Score)
{
break;
}
}
return index;
}
private int NewIndex(int i)
{
var n = Results.Count;
if (n > 0)
{
i = (n + i) % n;
return i;
}
else
{
// SelectedIndex returns -1 if selection is empty.
return -1;
}
}
public void SelectNextResult()
{
SelectedIndex = NewIndex(SelectedIndex + 1);
}
public void SelectPrevResult()
{
SelectedIndex = NewIndex(SelectedIndex - 1);
}
public void SelectNextPage()
{
SelectedIndex = NewIndex(SelectedIndex + _settings.MaxResultsToShow);
}
public void SelectPrevPage()
{
SelectedIndex = NewIndex(SelectedIndex - _settings.MaxResultsToShow);
}
public void SelectFirstResult()
{
SelectedIndex = NewIndex(0);
}
public void Clear()
{
Results.Clear();
}
public void RemoveResultsExcept(PluginMetadata metadata)
{
Results.RemoveAll(r => r.Result.PluginID != metadata.ID);
}
public void RemoveResultsFor(PluginMetadata metadata)
{
Results.RemoveAll(r => r.Result.PluginID == metadata.ID);
}
public void SelectNextTabItem()
{
// Do nothing if there is no selected item or we've selected the next context button
if (!SelectedItem?.SelectNextContextButton() ?? true)
{
SelectNextResult();
}
}
public void SelectPrevTabItem()
{
// Do nothing if there is no selected item or we've selected the previous context button
if (!SelectedItem?.SelectPrevContextButton() ?? true)
{
// Tabbing backwards should highlight the last item of the previous row
SelectPrevResult();
SelectedItem.SelectLastContextButton();
}
}
public void SelectNextContextMenuItem()
{
if (SelectedItem != null)
{
if (!SelectedItem.SelectNextContextButton())
{
SelectedItem.SelectLastContextButton();
}
}
}
public void SelectPreviousContextMenuItem()
{
if (SelectedItem != null)
{
SelectedItem.SelectPrevContextButton();
}
}
public bool IsContextMenuItemSelected()
{
if (SelectedItem != null && SelectedItem.ContextMenuSelectedIndex != ResultViewModel.NoSelectionIndex)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// Add new results to ResultCollection
/// </summary>
public void AddResults(List<Result> newRawResults, CancellationToken ct)
{
if (newRawResults == null)
{
throw new ArgumentNullException(nameof(newRawResults));
}
List<ResultViewModel> newResults = new List<ResultViewModel>(newRawResults.Count);
foreach (Result r in newRawResults)
{
newResults.Add(new ResultViewModel(r));
ct.ThrowIfCancellationRequested();
}
Results.AddRange(newResults);
}
public void Sort()
{
var sorted = Results.OrderByDescending(x => x.Result.Score).ToList();
Clear();
Results.AddRange(sorted);
}
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(Inline),
typeof(ResultsViewModel),
new PropertyMetadata(null, FormattedTextPropertyChanged));
public static void SetFormattedText(DependencyObject textBlock, IList<int> value)
{
if (textBlock != null)
{
textBlock.SetValue(FormattedTextProperty, value);
}
}
public static Inline GetFormattedText(DependencyObject textBlock)
{
return (Inline)textBlock?.GetValue(FormattedTextProperty);
}
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBlock = d as TextBlock;
if (textBlock == null)
{
return;
}
var inline = (Inline)e.NewValue;
textBlock.Inlines.Clear();
if (inline == null)
{
return;
}
textBlock.Inlines.Add(inline);
}
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using PowerLauncher.Helper;
using Wox.Infrastructure.UserSettings;
using Wox.Plugin;
namespace PowerLauncher.ViewModel
{
public class ResultsViewModel : BaseModel
{
private readonly object _collectionLock = new object();
private readonly Settings _settings;
public ResultsViewModel()
{
Results = new ResultCollection();
BindingOperations.EnableCollectionSynchronization(Results, _collectionLock);
}
public ResultsViewModel(Settings settings)
: this()
{
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
_settings.PropertyChanged += (s, e) =>
{
if (e.PropertyName == nameof(_settings.MaxResultsToShow))
{
Application.Current.Dispatcher.Invoke(() =>
{
OnPropertyChanged(nameof(MaxHeight));
});
}
};
}
public int MaxHeight
{
get
{
return _settings.MaxResultsToShow * 75;
}
}
public int SelectedIndex { get; set; }
private ResultViewModel _selectedItem;
public ResultViewModel SelectedItem
{
get
{
return _selectedItem;
}
set
{
if (value != null)
{
if (_selectedItem != null)
{
_selectedItem.DeactivateContextButtons(ResultViewModel.ActivationType.Selection);
}
_selectedItem = value;
_selectedItem.ActivateContextButtons(ResultViewModel.ActivationType.Selection);
}
else
{
_selectedItem = value;
}
}
}
public Thickness Margin { get; set; }
public Visibility Visibility { get; set; } = Visibility.Hidden;
public ResultCollection Results { get; }
private static int InsertIndexOf(int newScore, IList<ResultViewModel> list)
{
int index = 0;
for (; index < list.Count; index++)
{
var result = list[index];
if (newScore > result.Result.Score)
{
break;
}
}
return index;
}
private int NewIndex(int i)
{
var n = Results.Count;
if (n > 0)
{
i = (n + i) % n;
return i;
}
else
{
// SelectedIndex returns -1 if selection is empty.
return -1;
}
}
public void SelectNextResult()
{
SelectedIndex = NewIndex(SelectedIndex + 1);
}
public void SelectPrevResult()
{
SelectedIndex = NewIndex(SelectedIndex - 1);
}
public void SelectNextPage()
{
SelectedIndex = NewIndex(SelectedIndex + _settings.MaxResultsToShow);
}
public void SelectPrevPage()
{
SelectedIndex = NewIndex(SelectedIndex - _settings.MaxResultsToShow);
}
public void SelectFirstResult()
{
SelectedIndex = NewIndex(0);
}
public void Clear()
{
Results.Clear();
}
public void RemoveResultsExcept(PluginMetadata metadata)
{
Results.RemoveAll(r => r.Result.PluginID != metadata.ID);
}
public void RemoveResultsFor(PluginMetadata metadata)
{
Results.RemoveAll(r => r.Result.PluginID == metadata.ID);
}
public void SelectNextTabItem()
{
// Do nothing if there is no selected item or we've selected the next context button
if (!SelectedItem?.SelectNextContextButton() ?? true)
{
SelectNextResult();
}
}
public void SelectPrevTabItem()
{
// Do nothing if there is no selected item or we've selected the previous context button
if (!SelectedItem?.SelectPrevContextButton() ?? true)
{
// Tabbing backwards should highlight the last item of the previous row
SelectPrevResult();
SelectedItem.SelectLastContextButton();
}
}
public void SelectNextContextMenuItem()
{
if (SelectedItem != null)
{
if (!SelectedItem.SelectNextContextButton())
{
SelectedItem.SelectLastContextButton();
}
}
}
public void SelectPreviousContextMenuItem()
{
if (SelectedItem != null)
{
SelectedItem.SelectPrevContextButton();
}
}
public bool IsContextMenuItemSelected()
{
if (SelectedItem != null && SelectedItem.ContextMenuSelectedIndex != ResultViewModel.NoSelectionIndex)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// Add new results to ResultCollection
/// </summary>
public void AddResults(List<Result> newRawResults, CancellationToken ct)
{
if (newRawResults == null)
{
throw new ArgumentNullException(nameof(newRawResults));
}
List<ResultViewModel> newResults = new List<ResultViewModel>(newRawResults.Count);
foreach (Result r in newRawResults)
{
newResults.Add(new ResultViewModel(r));
ct.ThrowIfCancellationRequested();
}
Results.AddRange(newResults);
}
public void Sort()
{
var sorted = Results.OrderByDescending(x => x.Result.Score).ToList();
Clear();
Results.AddRange(sorted);
}
public static readonly DependencyProperty FormattedTextProperty = DependencyProperty.RegisterAttached(
"FormattedText",
typeof(Inline),
typeof(ResultsViewModel),
new PropertyMetadata(null, FormattedTextPropertyChanged));
public static void SetFormattedText(DependencyObject textBlock, IList<int> value)
{
if (textBlock != null)
{
textBlock.SetValue(FormattedTextProperty, value);
}
}
public static Inline GetFormattedText(DependencyObject textBlock)
{
return (Inline)textBlock?.GetValue(FormattedTextProperty);
}
private static void FormattedTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var textBlock = d as TextBlock;
if (textBlock == null)
{
return;
}
var inline = (Inline)e.NewValue;
textBlock.Inlines.Clear();
if (inline == null)
{
return;
}
textBlock.Inlines.Add(inline);
}
}
}

View File

@@ -1,14 +1,14 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.Plugin.Program.UnitTests")]
namespace Wox.Infrastructure
{
public class StringMatcher
@@ -61,11 +61,11 @@ namespace Wox.Infrastructure
/// </summary>
public MatchResult FuzzyMatch(string query, string stringToCompare, MatchOption opt)
{
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query))
{
return new MatchResult(false, UserSettingSearchPrecision);
}
if (string.IsNullOrEmpty(stringToCompare) || string.IsNullOrEmpty(query))
{
return new MatchResult(false, UserSettingSearchPrecision);
}
query = query.Trim();
if (_alphabet != null)
@@ -76,8 +76,8 @@ namespace Wox.Infrastructure
var fullStringToCompareWithoutCase = opt.IgnoreCase ? stringToCompare.ToLower() : stringToCompare;
var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query;
var queryWithoutCase = opt.IgnoreCase ? query.ToLower() : query;
var querySubstrings = queryWithoutCase.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
int currentQuerySubstringIndex = 0;
var currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
@@ -151,18 +151,18 @@ namespace Wox.Infrastructure
currentQuerySubstringIndex++;
allQuerySubstringsMatched = AllQuerySubstringsMatched(currentQuerySubstringIndex, querySubstrings.Length);
if (allQuerySubstringsMatched)
{
break;
}
// otherwise move to the next query substring
if (allQuerySubstringsMatched)
{
break;
}
// otherwise move to the next query substring
currentQuerySubstring = querySubstrings[currentQuerySubstringIndex];
currentQuerySubstringCharacterIndex = 0;
}
}
// proceed to calculate score if every char or substring without whitespaces matched
}
// proceed to calculate score if every char or substring without whitespaces matched
if (allQuerySubstringsMatched)
{
var nearestSpaceIndex = CalculateClosestSpaceIndex(spaceIndices, firstMatchIndex);
@@ -202,8 +202,8 @@ namespace Wox.Infrastructure
}
return allMatch;
}
}
private static List<int> GetUpdatedIndexList(int startIndexToVerify, int currentQuerySubstringCharacterIndex, int firstMatchIndexInWord, List<int> indexList)
{
var updatedList = new List<int>();

View File

@@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.ObjectModel;
using System.Drawing;
@@ -15,22 +15,22 @@ namespace Wox.Infrastructure.UserSettings
{
private string _hotkey = "Alt + Space";
private string _previousHotkey = string.Empty;
public string PreviousHotkey
public string PreviousHotkey
{
get
{
return _previousHotkey;
}
}
public string Hotkey
public string Hotkey
{
get
{
return _hotkey;
}
set
{
if (_hotkey != value)
@@ -41,27 +41,27 @@ namespace Wox.Infrastructure.UserSettings
}
}
}
public string Language { get; set; } = "en";
public string Theme { get; set; } = "Dark";
public string QueryBoxFont { get; set; } = FontFamily.GenericSansSerif.Name;
public string QueryBoxFontStyle { get; set; }
public string QueryBoxFontWeight { get; set; }
public string QueryBoxFontStretch { get; set; }
public string ResultFont { get; set; } = FontFamily.GenericSansSerif.Name;
public string ResultFontStyle { get; set; }
public string ResultFontWeight { get; set; }
public string ResultFontStretch { get; set; }
/// <summary>
/// Gets or sets a value indicating whether when false Alphabet static service will always return empty results
/// </summary>
@@ -72,13 +72,13 @@ namespace Wox.Infrastructure.UserSettings
[JsonIgnore]
public string QuerySearchPrecisionString
{
get
{
return QuerySearchPrecision.ToString();
}
set
{
get
{
return QuerySearchPrecision.ToString();
}
set
{
try
{
var precisionScore = (StringMatcher.SearchPrecisionScore)Enum
@@ -95,41 +95,41 @@ namespace Wox.Infrastructure.UserSettings
StringMatcher.Instance.UserSettingSearchPrecision = StringMatcher.SearchPrecisionScore.Regular;
throw;
}
}
}
}
}
public bool AutoUpdates { get; set; } = false;
public double WindowLeft { get; set; }
public double WindowTop { get; set; }
private int _maxResultsToShow = 4;
public int MaxResultsToShow
{
get
{
return _maxResultsToShow;
}
set
public int MaxResultsToShow
{
get
{
if (_maxResultsToShow != value)
{
_maxResultsToShow = value;
OnPropertyChanged();
return _maxResultsToShow;
}
set
{
if (_maxResultsToShow != value)
{
_maxResultsToShow = value;
OnPropertyChanged();
}
}
}
}
public int ActivateTimes { get; set; }
// Order defaults to 0 or -1, so 1 will let this property appear last
[JsonProperty(Order = 1)]
public PluginSettings PluginSettings { get; set; } = new PluginSettings();
public ObservableCollection<CustomPluginHotkey> CustomPluginHotkeys { get; set; } = new ObservableCollection<CustomPluginHotkey>();
[Obsolete]
@@ -139,37 +139,37 @@ namespace Wox.Infrastructure.UserSettings
public OpacityMode OpacityMode { get; set; } = OpacityMode.Normal;
public bool DontPromptUpdateMsg { get; set; }
public bool EnableUpdateLog { get; set; }
public bool StartWoxOnSystemStartup { get; set; } = true;
public bool HideOnStartup { get; set; }
private bool _hideNotifyIcon;
public bool HideNotifyIcon
{
get
{
return _hideNotifyIcon;
}
set
{
_hideNotifyIcon = value;
OnPropertyChanged();
}
get
{
return _hideNotifyIcon;
}
set
{
_hideNotifyIcon = value;
OnPropertyChanged();
}
}
public bool LeaveCmdOpen { get; set; }
public bool HideWhenDeactivated { get; set; } = true;
public bool ClearInputOnLaunch { get; set; } = false;
public bool RememberLastLaunchLocation { get; set; }
public bool IgnoreHotkeysOnFullscreen { get; set; }
public HttpProxy Proxy { get; set; } = new HttpProxy();
@@ -192,4 +192,4 @@ namespace Wox.Infrastructure.UserSettings
LayeredWindow = 1,
DWM = 2,
}
}
}

View File

@@ -1,9 +1,9 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Windows.Input;
using System.Windows.Input;
namespace Wox.Plugin
{
@@ -15,8 +15,8 @@ namespace Wox.Plugin
public string Glyph { get; set; }
public string FontFamily { get; set; }
public string FontFamily { get; set; }
public Key AcceleratorKey { get; set; }
public ModifierKeys AcceleratorModifiers { get; set; }
@@ -31,4 +31,4 @@ namespace Wox.Plugin
return Title;
}
}
}
}

View File

@@ -1,16 +1,16 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Windows.Controls;
using Microsoft.PowerToys.Settings.UI.Lib;
namespace Wox.Plugin
{
public interface ISettingProvider
{
Control CreateSettingPanel();
void UpdateSettings(PowerLauncherSettings settings);
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Windows.Controls;
using Microsoft.PowerToys.Settings.UI.Lib;
namespace Wox.Plugin
{
public interface ISettingProvider
{
Control CreateSettingPanel();
void UpdateSettings(PowerLauncherSettings settings);
}
}

View File

@@ -1,165 +1,165 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Media;
namespace Wox.Plugin
{
public class Result
{
private string _title;
private ToolTipData _toolTipData;
private string _pluginDirectory;
private string _icoPath;
public string Title
{
get
{
return _title;
}
set
{
_title = value.Replace("\n", " ");
}
}
public string SubTitle { get; set; }
public string Glyph { get; set; }
public string FontFamily { get; set; }
public Visibility ToolTipVisibility { get; set; } = Visibility.Collapsed;
public ToolTipData ToolTipData
{
get
{
return _toolTipData;
}
set
{
_toolTipData = value;
ToolTipVisibility = Visibility.Visible;
}
}
/// <summary>
/// Gets or sets the text that will get displayed in the Search text box, when this item is selected in the result list.
/// </summary>
public string QueryTextDisplay { get; set; }
public string IcoPath
{
get
{
return _icoPath;
}
set
{
if (!string.IsNullOrEmpty(PluginDirectory) && !Path.IsPathRooted(value))
{
_icoPath = Path.Combine(value, IcoPath);
}
else
{
_icoPath = value;
}
}
}
public delegate ImageSource IconDelegate();
public IconDelegate Icon { get; set; }
/// <summary>
/// Gets or sets return true to hide wox after select result
/// </summary>
public Func<ActionContext, bool> Action { get; set; }
public int Score { get; set; }
/// <summary>
/// Gets or sets a list of indexes for the characters to be highlighted in Title
/// </summary>
public IList<int> TitleHighlightData { get; set; }
/// <summary>
/// Gets or sets a list of indexes for the characters to be highlighted in SubTitle
/// </summary>
public IList<int> SubTitleHighlightData { get; set; }
/// <summary>
/// Gets or sets only results that originQuery match with current query will be displayed in the panel
/// </summary>
internal Query OriginQuery { get; set; }
/// <summary>
/// Gets or sets plugin directory
/// </summary>
public string PluginDirectory
{
get
{
return _pluginDirectory;
}
set
{
_pluginDirectory = value;
if (!string.IsNullOrEmpty(IcoPath) && !Path.IsPathRooted(IcoPath))
{
IcoPath = Path.Combine(value, IcoPath);
}
}
}
public override bool Equals(object obj)
{
var r = obj as Result;
var equality = string.Equals(r?.Title, Title) &&
string.Equals(r?.SubTitle, SubTitle) &&
string.Equals(r?.IcoPath, IcoPath) &&
TitleHighlightData == r.TitleHighlightData &&
SubTitleHighlightData == r.SubTitleHighlightData;
return equality;
}
public override int GetHashCode()
{
var hashcode = (Title?.GetHashCode() ?? 0) ^
(SubTitle?.GetHashCode() ?? 0);
return hashcode;
}
public override string ToString()
{
return Title + SubTitle;
}
public Result()
{
}
/// <summary>
/// Gets or sets additional data associate with this result
/// </summary>
public object ContextData { get; set; }
/// <summary>
/// Gets plugin ID that generated this result
/// </summary>
public string PluginID { get; internal set; }
}
}
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Media;
namespace Wox.Plugin
{
public class Result
{
private string _title;
private ToolTipData _toolTipData;
private string _pluginDirectory;
private string _icoPath;
public string Title
{
get
{
return _title;
}
set
{
_title = value.Replace("\n", " ");
}
}
public string SubTitle { get; set; }
public string Glyph { get; set; }
public string FontFamily { get; set; }
public Visibility ToolTipVisibility { get; set; } = Visibility.Collapsed;
public ToolTipData ToolTipData
{
get
{
return _toolTipData;
}
set
{
_toolTipData = value;
ToolTipVisibility = Visibility.Visible;
}
}
/// <summary>
/// Gets or sets the text that will get displayed in the Search text box, when this item is selected in the result list.
/// </summary>
public string QueryTextDisplay { get; set; }
public string IcoPath
{
get
{
return _icoPath;
}
set
{
if (!string.IsNullOrEmpty(PluginDirectory) && !Path.IsPathRooted(value))
{
_icoPath = Path.Combine(value, IcoPath);
}
else
{
_icoPath = value;
}
}
}
public delegate ImageSource IconDelegate();
public IconDelegate Icon { get; set; }
/// <summary>
/// Gets or sets return true to hide wox after select result
/// </summary>
public Func<ActionContext, bool> Action { get; set; }
public int Score { get; set; }
/// <summary>
/// Gets or sets a list of indexes for the characters to be highlighted in Title
/// </summary>
public IList<int> TitleHighlightData { get; set; }
/// <summary>
/// Gets or sets a list of indexes for the characters to be highlighted in SubTitle
/// </summary>
public IList<int> SubTitleHighlightData { get; set; }
/// <summary>
/// Gets or sets only results that originQuery match with current query will be displayed in the panel
/// </summary>
internal Query OriginQuery { get; set; }
/// <summary>
/// Gets or sets plugin directory
/// </summary>
public string PluginDirectory
{
get
{
return _pluginDirectory;
}
set
{
_pluginDirectory = value;
if (!string.IsNullOrEmpty(IcoPath) && !Path.IsPathRooted(IcoPath))
{
IcoPath = Path.Combine(value, IcoPath);
}
}
}
public override bool Equals(object obj)
{
var r = obj as Result;
var equality = string.Equals(r?.Title, Title) &&
string.Equals(r?.SubTitle, SubTitle) &&
string.Equals(r?.IcoPath, IcoPath) &&
TitleHighlightData == r.TitleHighlightData &&
SubTitleHighlightData == r.SubTitleHighlightData;
return equality;
}
public override int GetHashCode()
{
var hashcode = (Title?.GetHashCode() ?? 0) ^
(SubTitle?.GetHashCode() ?? 0);
return hashcode;
}
public override string ToString()
{
return Title + SubTitle;
}
public Result()
{
}
/// <summary>
/// Gets or sets additional data associate with this result
/// </summary>
public object ContextData { get; set; }
/// <summary>
/// Gets plugin ID that generated this result
/// </summary>
public string PluginID { get; internal set; }
}
}