mirror of
https://github.com/microsoft/PowerToys
synced 2025-08-31 14:35:18 +00:00
Enable Static Analysis on programs plugin (#5847)
* Removing unused ProgramSettings constructor paramaters. * Fix for: Severity Code Description Project File Line Suppression State Error CA1829 Use the "Count" property instead of Enumerable.Count(). Microsoft.Plugin.Program C:\Repos\PowerToys\src\modules\launcher\Plugins\Microsoft.Plugin.Program\Views\ProgramSetting.xaml.cs 182 Active Severity Code Description Project File Line Suppression State Error CA1827 Count() is used where Any() could be used instead to improve performance. Microsoft.Plugin.Program C:\Repos\PowerToys\src\modules\launcher\Plugins\Microsoft.Plugin.Program\Views\ProgramSetting.xaml.cs 287 Active * Fixes for CA1031: https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1031?view=vs-2019 * More fixes(Suppression) for CA1031 (Catching base exception) https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1031?view=vs-2019 * Fix for nested types being externally visible. CA1034 https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1034?view=vs-2019 * Fix for CA1028 If possible, make the underlying type of Hresult System.Int32 instead of uint. Microsoft.Plugin.Program C:\Repos\PowerToys\src\modules\launcher\Plugins\Microsoft.Plugin.Program\Programs\UWP.cs 236 Active * Fix for CA2227 Collection properties should be readonly. https://docs.microsoft.com/en-us/visualstudio/code-quality/ca2227?view=vs-2019 Fix rror CA2211 Non-constant fields should not be visible https://docs.microsoft.com/en-us/visualstudio/code-quality/ca2211?view=vs-2019 * CA2208: Instantiate argument exceptions correctly https://docs.microsoft.com/en-us/visualstudio/code-quality/ca2208?view=vs-2019 * Win32.cs Static analysis fixes: CA2200: Rethrow to preserve stack details https://docs.microsoft.com/en-us/visualstudio/code-quality/ca2200?view=vs-2019 CA1825: Avoid zero-length array allocations https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1825?view=vs-2019 CA2211: Non-constant fields should not be visible https://docs.microsoft.com/en-us/visualstudio/code-quality/ca2211?view=vs-2019 * More Win32.cs static analysis fixes: CA1307: Specify StringComparison https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1307?view=vs-2019 Verifying query is not null before using it. CA1062: Validate arguments of public methods https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1062?view=vs-2019 CA1305: Specify IFormatProvider https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1305?view=vs-2019 * UWPApplication. CA2007: Do not directly await a Task https://docs.microsoft.com/en-us/visualstudio/code-quality/ca2007?view=vs-2019 * Error CA2010 Consume the hresult returned by method 'Microsoft.Plugin.Program.Programs.AppxPackageHelper.IAppxManifestApplication.GetStringValue(string, out string)' and call Marshal.ThrowExceptionForHR. Note: CA2010 has been removed from future FXCop versions as it can provide incorrect advice for methods that don't return hresult (possibly when [PreserveSig] has been incorrectly labeled.) I've verified the methods in question do still return hresults. * More fixes for Error CA2010 Consume the hresult returned by method and call Marshal.ThrowExceptionForHR. * Fixes for: Error CA2000 Call System.IDisposable.Dispose on object created before all references to it are out of scope. * Suppress CA1031:Do not catch general exception types * Fixes for Error CA1034 Do not nest type ... * Fixing Unit tests that were broken as per a previous fix. * Fix for: CA1034: Nested types should not be visible: https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1034?view=vs-2019 * Suppressing CA1707 for native/com types. CA1707:Identifiers should not contain underscores * Fix for Error CA1307 The behavior of 'string.Replace(string, string?)' could vary based on the current user's locale settings. * Fixes for Error CA1825 Avoid unnecessary zero-length array allocations. Use Array.Empty<string>() instead. * Fix for: Error CA1823 Unused field 'IndexLock'. * Fixes for CA1822: Mark members as static: https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1822?view=vs-2019 * Fix for Error CA1819 Properties should not return arrays * Fix for Error CA1806 ContextMenus calls Trim but does not use the new string instance that the method returns. Pass the instance as an argument to another method, assign the instance to a variable, or remove the call if it is unnecessary. * Fix for Error CA1801 Parameter settings of method UpdateSettings is never used. Remove the parameter or use it in the method body. * Fix for Error CA1724 The type name Settings conflicts in whole or in part with the namespace name 'Microsoft.PowerToys.Settings'. * Includes FxCop for static analysis * Fix for Error CA1717 Only FlagsAttribute enums should have plural names * Suppress Stgm flags: Error CA1714 Flags enums should have plural names Microsoft.Plugin.Program * Rename Win32 to Win32Program Fix for: Error CA1724 The type name Win32 conflicts in whole or in part with the namespace name 'Microsoft.Win32' defined in the .NET Framework. Rename the type to eliminate the conflict. * Fixes for Error CA1507 Use nameof in place of string literal * Using ordinal instead of invariant culture when not symbolic comparison * CA1308: Normalize strings to uppercase https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1308?view=vs-2019 * CA1304: Specify CultureInfo CA1305: Specify IFormatProvider https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1305?view=vs-2019 https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1304?view=vs-2019 * CA1062: Validate arguments of public methods https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1062?view=vs-2019 * CA1056: URI properties should not be strings https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1056?view=vs-2019 Fix: Making LogoURi private, as it is only used internally and operated on as a string * Warning CA1052 Type 'AppxPackageHelper' is a static holder type but is neither static nor NotInheritable * CA1060: Move P/Invokes to NativeMethods class https://docs.microsoft.com/en-us/visualstudio/code-quality/ca1060?view=vs-2019 * Programs Plugin - Treat warnings as errors true * CA1031:Do not catch general exception types Missed release only compile issue. Making debug and release behave the same. * Renaming 'Create' to 'CreateWin32Program' based on PR feedback.
This commit is contained in:
committed by
GitHub
parent
83de1686c0
commit
ac10c988b9
@@ -40,7 +40,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
public void All_ShouldReturnPackagesWithDevelopmentMode_WhenCalled()
|
||||
{
|
||||
// Arrange
|
||||
Main._settings = new Settings();
|
||||
Main._settings = new ProgramPluginSettings();
|
||||
List<IPackage> packages = new List<IPackage>() { developmentModeApp, packagedApp };
|
||||
var mock = new Mock<IPackageManager>();
|
||||
mock.Setup(x => x.FindPackagesForCurrentUser()).Returns(packages);
|
||||
@@ -59,7 +59,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
public void All_ShouldNotReturnPackageFrameworks_WhenCalled()
|
||||
{
|
||||
// Arrange
|
||||
Main._settings = new Settings();
|
||||
Main._settings = new ProgramPluginSettings();
|
||||
List<IPackage> packages = new List<IPackage>() { frameworkApp, packagedApp };
|
||||
var mock = new Mock<IPackageManager>();
|
||||
mock.Setup(x => x.FindPackagesForCurrentUser()).Returns(packages);
|
||||
@@ -78,7 +78,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
{
|
||||
// Arrange
|
||||
PackageWrapper invalidPackagedApp = new PackageWrapper();
|
||||
Main._settings = new Settings();
|
||||
Main._settings = new ProgramPluginSettings();
|
||||
List<IPackage> packages = new List<IPackage>() { invalidPackagedApp };
|
||||
var mock = new Mock<IPackageManager>();
|
||||
mock.Setup(x => x.FindPackagesForCurrentUser()).Returns(packages);
|
||||
|
@@ -10,12 +10,12 @@ using System.IO.Packaging;
|
||||
using Windows.ApplicationModel;
|
||||
namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
{
|
||||
using Win32 = Microsoft.Plugin.Program.Programs.Win32;
|
||||
using Win32Program = Microsoft.Plugin.Program.Programs.Win32Program;
|
||||
|
||||
[TestFixture]
|
||||
public class Win32Tests
|
||||
{
|
||||
static Win32 notepad_appdata = new Win32
|
||||
static Win32Program notepad_appdata = new Win32Program
|
||||
{
|
||||
Name = "Notepad",
|
||||
ExecutableName = "notepad.exe",
|
||||
@@ -24,7 +24,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 2
|
||||
};
|
||||
|
||||
static Win32 notepad_users = new Win32
|
||||
static Win32Program notepad_users = new Win32Program
|
||||
{
|
||||
Name = "Notepad",
|
||||
ExecutableName = "notepad.exe",
|
||||
@@ -33,7 +33,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 2
|
||||
};
|
||||
|
||||
static Win32 azure_command_prompt = new Win32
|
||||
static Win32Program azure_command_prompt = new Win32Program
|
||||
{
|
||||
Name = "Microsoft Azure Command Prompt - v2.9",
|
||||
ExecutableName = "cmd.exe",
|
||||
@@ -42,7 +42,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 2
|
||||
};
|
||||
|
||||
static Win32 visual_studio_command_prompt = new Win32
|
||||
static Win32Program visual_studio_command_prompt = new Win32Program
|
||||
{
|
||||
Name = "x64 Native Tools Command Prompt for VS 2019",
|
||||
ExecutableName = "cmd.exe",
|
||||
@@ -51,7 +51,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 2
|
||||
};
|
||||
|
||||
static Win32 command_prompt = new Win32
|
||||
static Win32Program command_prompt = new Win32Program
|
||||
{
|
||||
Name = "Command Prompt",
|
||||
ExecutableName = "cmd.exe",
|
||||
@@ -60,7 +60,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 2
|
||||
};
|
||||
|
||||
static Win32 file_explorer = new Win32
|
||||
static Win32Program file_explorer = new Win32Program
|
||||
{
|
||||
Name = "File Explorer",
|
||||
ExecutableName = "File Explorer.lnk",
|
||||
@@ -69,7 +69,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 2
|
||||
};
|
||||
|
||||
static Win32 wordpad = new Win32
|
||||
static Win32Program wordpad = new Win32Program
|
||||
{
|
||||
Name = "Wordpad",
|
||||
ExecutableName = "wordpad.exe",
|
||||
@@ -78,7 +78,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 2
|
||||
};
|
||||
|
||||
static Win32 wordpad_duplicate = new Win32
|
||||
static Win32Program wordpad_duplicate = new Win32Program
|
||||
{
|
||||
Name = "WORDPAD",
|
||||
ExecutableName = "WORDPAD.EXE",
|
||||
@@ -87,7 +87,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 2
|
||||
};
|
||||
|
||||
static Win32 twitter_pwa = new Win32
|
||||
static Win32Program twitter_pwa = new Win32Program
|
||||
{
|
||||
Name = "Twitter",
|
||||
FullPath = "c:\\program files (x86)\\google\\chrome\\application\\chrome_proxy.exe",
|
||||
@@ -96,7 +96,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 0
|
||||
};
|
||||
|
||||
static Win32 pinned_webpage = new Win32
|
||||
static Win32Program pinned_webpage = new Win32Program
|
||||
{
|
||||
Name = "Web page",
|
||||
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe",
|
||||
@@ -105,7 +105,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 0
|
||||
};
|
||||
|
||||
static Win32 edge_named_pinned_webpage = new Win32
|
||||
static Win32Program edge_named_pinned_webpage = new Win32Program
|
||||
{
|
||||
Name = "edge - Bing",
|
||||
FullPath = "c:\\program files (x86)\\microsoft\\edge\\application\\msedge_proxy.exe",
|
||||
@@ -114,7 +114,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 0
|
||||
};
|
||||
|
||||
static Win32 msedge = new Win32
|
||||
static Win32Program msedge = new Win32Program
|
||||
{
|
||||
Name = "Microsoft Edge",
|
||||
ExecutableName = "msedge.exe",
|
||||
@@ -123,7 +123,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 2
|
||||
};
|
||||
|
||||
static Win32 chrome = new Win32
|
||||
static Win32Program chrome = new Win32Program
|
||||
{
|
||||
Name = "Google Chrome",
|
||||
ExecutableName = "chrome.exe",
|
||||
@@ -132,7 +132,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 2
|
||||
};
|
||||
|
||||
static Win32 dummy_proxy_app = new Win32
|
||||
static Win32Program dummy_proxy_app = new Win32Program
|
||||
{
|
||||
Name = "Proxy App",
|
||||
ExecutableName = "test_proxy.exe",
|
||||
@@ -141,7 +141,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 2
|
||||
};
|
||||
|
||||
static Win32 cmd_run_command = new Win32
|
||||
static Win32Program cmd_run_command = new Win32Program
|
||||
{
|
||||
Name = "cmd",
|
||||
ExecutableName = "cmd.exe",
|
||||
@@ -150,7 +150,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 3 // Run command
|
||||
};
|
||||
|
||||
static Win32 cmder_run_command = new Win32
|
||||
static Win32Program cmder_run_command = new Win32Program
|
||||
{
|
||||
Name = "Cmder",
|
||||
Description = "Cmder: Lovely Console Emulator",
|
||||
@@ -160,7 +160,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 3 // Run command
|
||||
};
|
||||
|
||||
static Win32 dummy_internetShortcut_app = new Win32
|
||||
static Win32Program dummy_internetShortcut_app = new Win32Program
|
||||
{
|
||||
Name = "Shop Titans",
|
||||
ExecutableName = "Shop Titans.url",
|
||||
@@ -170,7 +170,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
AppType = 1
|
||||
};
|
||||
|
||||
static Win32 dummy_internetShortcut_app_duplicate = new Win32
|
||||
static Win32Program dummy_internetShortcut_app_duplicate = new Win32Program
|
||||
{
|
||||
Name = "Shop Titans",
|
||||
ExecutableName = "Shop Titans.url",
|
||||
@@ -184,12 +184,12 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
public void DedupFunction_whenCalled_mustRemoveDuplicateNotepads()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32> prgms = new List<Win32>();
|
||||
List<Win32Program> prgms = new List<Win32Program>();
|
||||
prgms.Add(notepad_appdata);
|
||||
prgms.Add(notepad_users);
|
||||
|
||||
// Act
|
||||
Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel());
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 1);
|
||||
@@ -199,12 +199,12 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
public void DedupFunction_whenCalled_MustRemoveInternetShortcuts()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32> prgms = new List<Win32>();
|
||||
List<Win32Program> prgms = new List<Win32Program>();
|
||||
prgms.Add(dummy_internetShortcut_app);
|
||||
prgms.Add(dummy_internetShortcut_app_duplicate);
|
||||
|
||||
// Act
|
||||
Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel());
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 1);
|
||||
@@ -214,11 +214,11 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
public void DedupFunction_whenCalled_mustNotRemovelnkWhichdoesNotHaveExe()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32> prgms = new List<Win32>();
|
||||
List<Win32Program> prgms = new List<Win32Program>();
|
||||
prgms.Add(file_explorer);
|
||||
|
||||
// Act
|
||||
Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel());
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 1);
|
||||
@@ -228,12 +228,12 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
public void DedupFunction_mustRemoveDuplicates_forExeExtensionsWithoutLnkResolvedPath()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32> prgms = new List<Win32>();
|
||||
List<Win32Program> prgms = new List<Win32Program>();
|
||||
prgms.Add(wordpad);
|
||||
prgms.Add(wordpad_duplicate);
|
||||
|
||||
// Act
|
||||
Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel());
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 1);
|
||||
@@ -244,13 +244,13 @@ namespace Microsoft.Plugin.Program.UnitTests.Programs
|
||||
public void DedupFunction_mustNotRemovePrograms_withSameExeNameAndFullPath()
|
||||
{
|
||||
// Arrange
|
||||
List<Win32> prgms = new List<Win32>();
|
||||
List<Win32Program> prgms = new List<Win32Program>();
|
||||
prgms.Add(azure_command_prompt);
|
||||
prgms.Add(visual_studio_command_prompt);
|
||||
prgms.Add(command_prompt);
|
||||
|
||||
// Act
|
||||
Win32[] apps = Win32.DeduplicatePrograms(prgms.AsParallel());
|
||||
Win32Program[] apps = Win32Program.DeduplicatePrograms(prgms.AsParallel());
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(apps.Length, 3);
|
||||
|
@@ -13,13 +13,13 @@ using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
{
|
||||
using Win32 = Program.Programs.Win32;
|
||||
using Win32Program = Program.Programs.Win32Program;
|
||||
|
||||
[TestFixture]
|
||||
class Win32ProgramRepositoryTest
|
||||
{
|
||||
List<IFileSystemWatcherWrapper> _fileSystemWatchers;
|
||||
Settings _settings = new Settings();
|
||||
ProgramPluginSettings _settings = new ProgramPluginSettings();
|
||||
string[] _pathsToWatch = new string[] { "location1", "location2" };
|
||||
List<Mock<IFileSystemWatcherWrapper>> _fileSystemMocks;
|
||||
|
||||
@@ -40,9 +40,9 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
public void Win32Repository_MustNotStoreDuplicates_WhileAddingItemsWithSameHashCode(string name, string exename, string fullPath, string description1, string description2)
|
||||
{
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
|
||||
Win32 item1 = new Win32
|
||||
Win32Program item1 = new Win32Program
|
||||
{
|
||||
Name = name,
|
||||
ExecutableName = exename,
|
||||
@@ -50,7 +50,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
Description = description1
|
||||
};
|
||||
|
||||
Win32 item2 = new Win32
|
||||
Win32Program item2 = new Win32Program
|
||||
{
|
||||
Name = name,
|
||||
ExecutableName = exename,
|
||||
@@ -74,7 +74,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
public void Win32ProgramRepository_MustCallOnAppCreatedForApprefApps_WhenCreatedEventIsRaised(string path)
|
||||
{
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Created, "directory", path);
|
||||
|
||||
// Act
|
||||
@@ -89,11 +89,11 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
public void Win32ProgramRepository_MustCallOnAppDeletedForApprefApps_WhenDeletedEventIsRaised(string directory, string path)
|
||||
{
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Deleted, directory, path);
|
||||
|
||||
string fullPath = directory + "\\" + path;
|
||||
Win32 item = Win32.GetAppFromPath(fullPath);
|
||||
Win32Program item = Win32Program.GetAppFromPath(fullPath);
|
||||
_win32ProgramRepository.Add(item);
|
||||
|
||||
// Act
|
||||
@@ -107,14 +107,14 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
public void Win32ProgramRepository_MustCallOnAppRenamedForApprefApps_WhenRenamedEventIsRaised(string directory, string oldpath, string newpath)
|
||||
{
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
RenamedEventArgs e = new RenamedEventArgs(WatcherChangeTypes.Renamed, directory, newpath, oldpath);
|
||||
|
||||
string oldFullPath = directory + "\\" + oldpath;
|
||||
string newFullPath = directory + "\\" + newpath;
|
||||
|
||||
Win32 olditem = Win32.GetAppFromPath(oldFullPath);
|
||||
Win32 newitem = Win32.GetAppFromPath(newFullPath);
|
||||
Win32Program olditem = Win32Program.GetAppFromPath(oldFullPath);
|
||||
Win32Program newitem = Win32Program.GetAppFromPath(newFullPath);
|
||||
_win32ProgramRepository.Add(olditem);
|
||||
|
||||
// Act
|
||||
@@ -130,13 +130,13 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
public void Win32ProgramRepository_MustCallOnAppCreatedForExeApps_WhenCreatedEventIsRaised(string path)
|
||||
{
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Created, "directory", path);
|
||||
|
||||
// FileVersionInfo must be mocked for exe applications
|
||||
var mockFileVersionInfo = new Mock<IFileVersionInfoWrapper>();
|
||||
mockFileVersionInfo.Setup(m => m.GetVersionInfo(It.IsAny<string>())).Returns((FileVersionInfo)null);
|
||||
Win32._fileVersionInfoWrapper = mockFileVersionInfo.Object;
|
||||
Win32Program.FileVersionInfoWrapper = mockFileVersionInfo.Object;
|
||||
|
||||
// Act
|
||||
_fileSystemMocks[0].Raise(m => m.Created += null, e);
|
||||
@@ -150,16 +150,16 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
public void Win32ProgramRepository_MustCallOnAppDeletedForExeApps_WhenDeletedEventIsRaised(string directory, string path)
|
||||
{
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Deleted, directory, path);
|
||||
|
||||
// FileVersionInfo must be mocked for exe applications
|
||||
var mockFileVersionInfo = new Mock<IFileVersionInfoWrapper>();
|
||||
mockFileVersionInfo.Setup(m => m.GetVersionInfo(It.IsAny<string>())).Returns((FileVersionInfo)null);
|
||||
Win32._fileVersionInfoWrapper = mockFileVersionInfo.Object;
|
||||
Win32Program.FileVersionInfoWrapper = mockFileVersionInfo.Object;
|
||||
|
||||
string fullPath = directory + "\\" + path;
|
||||
Win32 item = Win32.GetAppFromPath(fullPath);
|
||||
Win32Program item = Win32Program.GetAppFromPath(fullPath);
|
||||
_win32ProgramRepository.Add(item);
|
||||
|
||||
// Act
|
||||
@@ -173,7 +173,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
public void Win32ProgramRepository_MustCallOnAppRenamedForExeApps_WhenRenamedEventIsRaised(string directory, string oldpath, string newpath)
|
||||
{
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
RenamedEventArgs e = new RenamedEventArgs(WatcherChangeTypes.Renamed, directory, newpath, oldpath);
|
||||
|
||||
string oldFullPath = directory + "\\" + oldpath;
|
||||
@@ -182,10 +182,10 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
// FileVersionInfo must be mocked for exe applications
|
||||
var mockFileVersionInfo = new Mock<IFileVersionInfoWrapper>();
|
||||
mockFileVersionInfo.Setup(m => m.GetVersionInfo(It.IsAny<string>())).Returns((FileVersionInfo)null);
|
||||
Win32._fileVersionInfoWrapper = mockFileVersionInfo.Object;
|
||||
Win32Program.FileVersionInfoWrapper = mockFileVersionInfo.Object;
|
||||
|
||||
Win32 olditem = Win32.GetAppFromPath(oldFullPath);
|
||||
Win32 newitem = Win32.GetAppFromPath(newFullPath);
|
||||
Win32Program olditem = Win32Program.GetAppFromPath(oldFullPath);
|
||||
Win32Program newitem = Win32Program.GetAppFromPath(newFullPath);
|
||||
_win32ProgramRepository.Add(olditem);
|
||||
|
||||
// Act
|
||||
@@ -201,13 +201,13 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
public void Win32ProgramRepository_MustCallOnAppChangedForUrlApps_WhenChangedEventIsRaised(string path)
|
||||
{
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Changed, "directory", path);
|
||||
|
||||
// File.ReadAllLines must be mocked for url applications
|
||||
var mockFile = new Mock<IFileWrapper>();
|
||||
mockFile.Setup(m => m.ReadAllLines(It.IsAny<string>())).Returns(new string[] { "URL=steam://rungameid/1258080", "IconFile=iconFile" });
|
||||
Win32._fileWrapper = mockFile.Object;
|
||||
Win32Program.FileWrapper = mockFile.Object;
|
||||
|
||||
// Act
|
||||
_fileSystemMocks[0].Raise(m => m.Changed += null, e);
|
||||
@@ -223,13 +223,13 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
// We are handing internet shortcut apps using the Changed event instead
|
||||
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Created, "directory", path);
|
||||
|
||||
// File.ReadAllLines must be mocked for url applications
|
||||
var mockFile = new Mock<IFileWrapper>();
|
||||
mockFile.Setup(m => m.ReadAllLines(It.IsAny<string>())).Returns(new string[] { "URL=steam://rungameid/1258080", "IconFile=iconFile" });
|
||||
Win32._fileWrapper = mockFile.Object;
|
||||
Win32Program.FileWrapper = mockFile.Object;
|
||||
|
||||
// Act
|
||||
_fileSystemMocks[0].Raise(m => m.Created += null, e);
|
||||
@@ -246,18 +246,18 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
// We are handing internet shortcut apps using the Changed event instead
|
||||
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Changed, "directory", path);
|
||||
|
||||
// FileVersionInfo must be mocked for exe applications
|
||||
var mockFileVersionInfo = new Mock<IFileVersionInfoWrapper>();
|
||||
mockFileVersionInfo.Setup(m => m.GetVersionInfo(It.IsAny<string>())).Returns((FileVersionInfo)null);
|
||||
Win32._fileVersionInfoWrapper = mockFileVersionInfo.Object;
|
||||
Win32Program.FileVersionInfoWrapper = mockFileVersionInfo.Object;
|
||||
|
||||
// ShellLinkHelper must be mocked for lnk applications
|
||||
var mockShellLink = new Mock<IShellLinkHelper>();
|
||||
mockShellLink.Setup(m => m.RetrieveTargetPath(It.IsAny<string>())).Returns(String.Empty);
|
||||
Win32._helper = mockShellLink.Object;
|
||||
Win32Program.Helper = mockShellLink.Object;
|
||||
|
||||
// Act
|
||||
_fileSystemMocks[0].Raise(m => m.Changed += null, e);
|
||||
@@ -270,16 +270,16 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
public void Win32ProgramRepository_MustCallOnAppDeletedForUrlApps_WhenDeletedEventIsRaised(string directory, string path)
|
||||
{
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Deleted, directory, path);
|
||||
|
||||
// File.ReadAllLines must be mocked for url applications
|
||||
var mockFile = new Mock<IFileWrapper>();
|
||||
mockFile.Setup(m => m.ReadAllLines(It.IsAny<string>())).Returns(new string[] { "URL=steam://rungameid/1258080", "IconFile=iconFile" });
|
||||
Win32._fileWrapper = mockFile.Object;
|
||||
Win32Program.FileWrapper = mockFile.Object;
|
||||
|
||||
string fullPath = directory + "\\" + path;
|
||||
Win32 item = Win32.GetAppFromPath(fullPath);
|
||||
Win32Program item = Win32Program.GetAppFromPath(fullPath);
|
||||
_win32ProgramRepository.Add(item);
|
||||
|
||||
// Act
|
||||
@@ -293,19 +293,19 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
public void Win32ProgramRepository_MustCallOnAppRenamedForUrlApps_WhenRenamedEventIsRaised(string directory, string oldpath, string newpath)
|
||||
{
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
RenamedEventArgs e = new RenamedEventArgs(WatcherChangeTypes.Renamed, directory, newpath, oldpath);
|
||||
|
||||
// File.ReadAllLines must be mocked for url applications
|
||||
var mockFile = new Mock<IFileWrapper>();
|
||||
mockFile.Setup(m => m.ReadAllLines(It.IsAny<string>())).Returns(new string[] { "URL=steam://rungameid/1258080", "IconFile=iconFile" });
|
||||
Win32._fileWrapper = mockFile.Object;
|
||||
Win32Program.FileWrapper = mockFile.Object;
|
||||
|
||||
string oldFullPath = directory + "\\" + oldpath;
|
||||
string newFullPath = directory + "\\" + newpath;
|
||||
|
||||
Win32 olditem = Win32.GetAppFromPath(oldFullPath);
|
||||
Win32 newitem = Win32.GetAppFromPath(newFullPath);
|
||||
Win32Program olditem = Win32Program.GetAppFromPath(oldFullPath);
|
||||
Win32Program newitem = Win32Program.GetAppFromPath(newFullPath);
|
||||
_win32ProgramRepository.Add(olditem);
|
||||
|
||||
// Act
|
||||
@@ -322,13 +322,13 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
public void Win32ProgramRepository_MustCallOnAppCreatedForLnkApps_WhenCreatedEventIsRaised(string path)
|
||||
{
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Created, "directory", path);
|
||||
|
||||
// ShellLinkHelper must be mocked for lnk applications
|
||||
var mockShellLink = new Mock<IShellLinkHelper>();
|
||||
mockShellLink.Setup(m => m.RetrieveTargetPath(It.IsAny<string>())).Returns(String.Empty);
|
||||
Win32._helper = mockShellLink.Object;
|
||||
Win32Program.Helper = mockShellLink.Object;
|
||||
|
||||
// Act
|
||||
_fileSystemMocks[0].Raise(m => m.Created += null, e);
|
||||
@@ -342,16 +342,16 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
public void Win32ProgramRepository_MustCallOnAppDeletedForLnkApps_WhenDeletedEventIsRaised(string directory, string path)
|
||||
{
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
FileSystemEventArgs e = new FileSystemEventArgs(WatcherChangeTypes.Deleted, directory, path);
|
||||
|
||||
// ShellLinkHelper must be mocked for lnk applications
|
||||
var mockShellLink = new Mock<IShellLinkHelper>();
|
||||
mockShellLink.Setup(m => m.RetrieveTargetPath(It.IsAny<string>())).Returns(String.Empty);
|
||||
Win32._helper = mockShellLink.Object;
|
||||
Win32Program.Helper = mockShellLink.Object;
|
||||
|
||||
string fullPath = directory + "\\" + path;
|
||||
Win32 item = new Win32
|
||||
Win32Program item = new Win32Program
|
||||
{
|
||||
Name = "path",
|
||||
ExecutableName = "path.exe",
|
||||
@@ -372,7 +372,7 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
public void Win32ProgramRepository_MustCallOnAppRenamedForLnkApps_WhenRenamedEventIsRaised(string directory, string oldpath, string path)
|
||||
{
|
||||
// Arrange
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32>>("Win32"), _settings, _pathsToWatch);
|
||||
Win32ProgramRepository _win32ProgramRepository = new Win32ProgramRepository(_fileSystemWatchers, new BinaryStorage<IList<Win32Program>>("Win32"), _settings, _pathsToWatch);
|
||||
RenamedEventArgs e = new RenamedEventArgs(WatcherChangeTypes.Renamed, directory, path, oldpath);
|
||||
|
||||
string oldFullPath = directory + "\\" + oldpath;
|
||||
@@ -381,17 +381,17 @@ namespace Microsoft.Plugin.Program.UnitTests.Storage
|
||||
// ShellLinkHelper must be mocked for lnk applications
|
||||
var mockShellLink = new Mock<IShellLinkHelper>();
|
||||
mockShellLink.Setup(m => m.RetrieveTargetPath(It.IsAny<string>())).Returns(String.Empty);
|
||||
Win32._helper = mockShellLink.Object;
|
||||
Win32Program.Helper = mockShellLink.Object;
|
||||
|
||||
// old item and new item are the actual items when they are in existence
|
||||
Win32 olditem = new Win32
|
||||
Win32Program olditem = new Win32Program
|
||||
{
|
||||
Name = "oldpath",
|
||||
ExecutableName = path,
|
||||
FullPath = FullPath,
|
||||
};
|
||||
|
||||
Win32 newitem = new Win32
|
||||
Win32Program newitem = new Win32Program
|
||||
{
|
||||
Name = "path",
|
||||
ExecutableName = path,
|
||||
|
@@ -1,10 +1,10 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using Microsoft.Plugin.Program.Views.Models;
|
||||
using Microsoft.Plugin.Program.Views;
|
||||
using System.Linq;
|
||||
using Wox.Plugin;
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Plugin.Program
|
||||
{
|
||||
/// <summary>
|
||||
@@ -13,10 +13,10 @@ namespace Microsoft.Plugin.Program
|
||||
public partial class AddProgramSource
|
||||
{
|
||||
private PluginInitContext _context;
|
||||
private Settings.ProgramSource _editing;
|
||||
private Settings _settings;
|
||||
private ProgramSource _editing;
|
||||
private ProgramPluginSettings _settings;
|
||||
|
||||
public AddProgramSource(PluginInitContext context, Settings settings)
|
||||
public AddProgramSource(PluginInitContext context, ProgramPluginSettings settings)
|
||||
{
|
||||
InitializeComponent();
|
||||
_context = context;
|
||||
@@ -24,9 +24,9 @@ namespace Microsoft.Plugin.Program
|
||||
Directory.Focus();
|
||||
}
|
||||
|
||||
public AddProgramSource(Settings.ProgramSource edit, Settings settings)
|
||||
public AddProgramSource(ProgramSource edit, ProgramPluginSettings settings)
|
||||
{
|
||||
_editing = edit;
|
||||
_editing = edit ?? throw new ArgumentNullException(nameof(edit));
|
||||
_settings = settings;
|
||||
|
||||
InitializeComponent();
|
||||
@@ -35,11 +35,13 @@ namespace Microsoft.Plugin.Program
|
||||
|
||||
private void BrowseButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new FolderBrowserDialog();
|
||||
DialogResult result = dialog.ShowDialog();
|
||||
if (result == System.Windows.Forms.DialogResult.OK)
|
||||
{
|
||||
Directory.Text = dialog.SelectedPath;
|
||||
using (var dialog = new FolderBrowserDialog())
|
||||
{
|
||||
DialogResult result = dialog.ShowDialog();
|
||||
if (result == System.Windows.Forms.DialogResult.OK)
|
||||
{
|
||||
Directory.Text = dialog.SelectedPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -28,15 +28,17 @@ namespace Microsoft.Plugin.Program.Logger
|
||||
}
|
||||
|
||||
var configuration = new LoggingConfiguration();
|
||||
var target = new FileTarget();
|
||||
configuration.AddTarget("file", target);
|
||||
target.FileName = path.Replace(@"\", "/") + "/${shortdate}.txt";
|
||||
using (var target = new FileTarget())
|
||||
{
|
||||
configuration.AddTarget("file", target);
|
||||
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);
|
||||
var rule = new LoggingRule("*", LogLevel.Error, target);
|
||||
#endif
|
||||
configuration.LoggingRules.Add(rule);
|
||||
}
|
||||
LogManager.Configuration = configuration;
|
||||
}
|
||||
|
||||
|
@@ -20,28 +20,27 @@ namespace Microsoft.Plugin.Program
|
||||
{
|
||||
public class Main : IPlugin, IPluginI18n, IContextMenu, ISavable, IReloadable, IDisposable
|
||||
{
|
||||
private static readonly object IndexLock = new object();
|
||||
internal static Settings _settings { get; set; }
|
||||
internal static ProgramPluginSettings _settings { get; set; }
|
||||
|
||||
private static bool IsStartupIndexProgramsRequired => _settings.LastIndexTime.AddDays(3) < DateTime.Today;
|
||||
|
||||
private static PluginInitContext _context;
|
||||
|
||||
private readonly PluginJsonStorage<Settings> _settingsStorage;
|
||||
private readonly PluginJsonStorage<ProgramPluginSettings> _settingsStorage;
|
||||
private bool _disposed = false;
|
||||
private PackageRepository _packageRepository = new PackageRepository(new PackageCatalogWrapper(), new BinaryStorage<IList<UWP.Application>>("UWP"));
|
||||
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<Settings>();
|
||||
_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.Win32>>("Win32"), _settings, _win32ProgramRepositoryHelper._pathsToWatch);
|
||||
_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", () =>
|
||||
{
|
||||
@@ -95,7 +94,7 @@ namespace Microsoft.Plugin.Program
|
||||
|
||||
public void Init(PluginInitContext context)
|
||||
{
|
||||
_context = context;
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context)); ;
|
||||
_context.API.ThemeChanged += OnThemeChanged;
|
||||
UpdateUWPIconPath(_context.API.GetCurrentTheme());
|
||||
}
|
||||
@@ -107,7 +106,7 @@ namespace Microsoft.Plugin.Program
|
||||
|
||||
public void UpdateUWPIconPath(Theme theme)
|
||||
{
|
||||
foreach (UWP.Application app in _packageRepository)
|
||||
foreach (UWPApplication app in _packageRepository)
|
||||
{
|
||||
app.UpdatePath(theme);
|
||||
}
|
||||
@@ -135,6 +134,11 @@ namespace Microsoft.Plugin.Program
|
||||
|
||||
public List<ContextMenuResult> LoadContextMenus(Result selectedResult)
|
||||
{
|
||||
if(selectedResult == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(selectedResult));
|
||||
}
|
||||
|
||||
var menuOptions = new List<ContextMenuResult>();
|
||||
var program = selectedResult.ContextData as Programs.IProgram;
|
||||
if (program != null)
|
||||
@@ -145,10 +149,21 @@ namespace Microsoft.Plugin.Program
|
||||
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)
|
||||
@@ -164,7 +179,7 @@ namespace Microsoft.Plugin.Program
|
||||
IndexPrograms();
|
||||
}
|
||||
|
||||
public void UpdateSettings(PowerLauncherSettings settings)
|
||||
public static void UpdateSettings(PowerLauncherSettings _)
|
||||
{
|
||||
}
|
||||
|
||||
|
@@ -24,6 +24,7 @@
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Optimize>false</Optimize>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
@@ -36,6 +37,7 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -87,6 +89,10 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NLog" Version="4.7.2" />
|
||||
@@ -131,5 +137,6 @@
|
||||
<_Parameter1>Microsoft.Plugin.Program.UnitTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
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;
|
||||
|
||||
public bool EnableDesktopSource { get; set; } = true;
|
||||
|
||||
public bool EnableRegistrySource { get; set; } = true;
|
||||
|
||||
public bool EnablePathEnvironmentVariableSource { get; set; } = true;
|
||||
|
||||
public double MinScoreThreshold { get; set; } = 0.75;
|
||||
|
||||
internal const char SuffixSeparator = ';';
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains user added folder location contents as well as all user disabled applications
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Win32 class applications set UniqueIdentifier using their full file path</para>
|
||||
/// <para>UWP class applications set UniqueIdentifier using their Application User Model ID</para>
|
||||
/// <para>Custom user added program sources set UniqueIdentifier using their location</para>
|
||||
/// </remarks>
|
||||
public class ProgramSource
|
||||
{
|
||||
private string name;
|
||||
|
||||
public string Location { get; set; }
|
||||
public string Name { get => name ?? new DirectoryInfo(Location).Name; set => name = value; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
public string UniqueIdentifier { get; set; }
|
||||
}
|
||||
|
||||
public class DisabledProgramSource : ProgramSource { }
|
||||
}
|
@@ -1,4 +1,5 @@
|
||||
using System.Windows;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using Wox.Plugin;
|
||||
|
||||
namespace Microsoft.Plugin.Program
|
||||
@@ -9,14 +10,14 @@ namespace Microsoft.Plugin.Program
|
||||
public partial class ProgramSuffixes
|
||||
{
|
||||
private PluginInitContext context;
|
||||
private Settings _settings;
|
||||
private ProgramPluginSettings _settings;
|
||||
|
||||
public ProgramSuffixes(PluginInitContext context, Settings settings)
|
||||
public ProgramSuffixes(PluginInitContext context, ProgramPluginSettings settings)
|
||||
{
|
||||
this.context = context;
|
||||
this.context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
InitializeComponent();
|
||||
_settings = settings;
|
||||
tbSuffixes.Text = string.Join(Settings.SuffixSeparator.ToString(), _settings.ProgramSuffixes);
|
||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings));
|
||||
tbSuffixes.Text = string.Join(ProgramPluginSettings.SuffixSeparator.ToString(), _settings.ProgramSuffixes);
|
||||
}
|
||||
|
||||
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
|
||||
@@ -28,7 +29,8 @@ namespace Microsoft.Plugin.Program
|
||||
return;
|
||||
}
|
||||
|
||||
_settings.ProgramSuffixes = tbSuffixes.Text.Split(Settings.SuffixSeparator);
|
||||
_settings.ProgramSuffixes.Clear();
|
||||
_settings.ProgramSuffixes.AddRange(tbSuffixes.Text.Split(ProgramPluginSettings.SuffixSeparator));
|
||||
string msg = context.API.GetTranslation("wox_plugin_program_update_file_suffixes");
|
||||
MessageBox.Show(msg);
|
||||
|
||||
|
@@ -4,41 +4,39 @@ using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
public class ApplicationActivationHelper
|
||||
namespace Microsoft.Plugin.Program.Programs.ApplicationActivationHelper
|
||||
{
|
||||
// Reference : https://github.com/MicrosoftEdge/edge-launcher/blob/108e63df0b4cb5cd9d5e45aa7a264690851ec51d/MIcrosoftEdgeLauncherCsharp/Program.cs
|
||||
[Flags()]
|
||||
public enum ActivateOptions
|
||||
{
|
||||
// Reference : https://github.com/MicrosoftEdge/edge-launcher/blob/108e63df0b4cb5cd9d5e45aa7a264690851ec51d/MIcrosoftEdgeLauncherCsharp/Program.cs
|
||||
public enum ActivateOptions
|
||||
{
|
||||
None = 0x00000000,
|
||||
DesignMode = 0x00000001,
|
||||
NoErrorUI = 0x00000002,
|
||||
NoSplashScreen = 0x00000004,
|
||||
}
|
||||
None = 0x00000000,
|
||||
DesignMode = 0x00000001,
|
||||
NoErrorUI = 0x00000002,
|
||||
NoSplashScreen = 0x00000004,
|
||||
}
|
||||
|
||||
/// ApplicationActivationManager
|
||||
[ComImport, Guid("2e941141-7f97-4756-ba1d-9decde894a3d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
interface IApplicationActivationManager
|
||||
{
|
||||
IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId);
|
||||
IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId);
|
||||
IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId);
|
||||
}
|
||||
/// ApplicationActivationManager
|
||||
[ComImport, Guid("2e941141-7f97-4756-ba1d-9decde894a3d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IApplicationActivationManager
|
||||
{
|
||||
IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId);
|
||||
IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId);
|
||||
IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId);
|
||||
}
|
||||
|
||||
// Application Activation Manager Class
|
||||
[ComImport, Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
|
||||
public class ApplicationActivationManager : IApplicationActivationManager
|
||||
{
|
||||
// Application Activation Manager Class
|
||||
[ComImport, Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
|
||||
public class ApplicationActivationManager : IApplicationActivationManager
|
||||
{
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/]
|
||||
public extern IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/]
|
||||
public extern IntPtr ActivateApplication([In] String appUserModelId, [In] String arguments, [In] ActivateOptions options, [Out] out UInt32 processId);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public extern IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public extern IntPtr ActivateForFile([In] String appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] String verb, [Out] out UInt32 processId);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public extern IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId);
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public extern IntPtr ActivateForProtocol([In] String appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out UInt32 processId);
|
||||
}
|
||||
}
|
||||
|
@@ -4,13 +4,14 @@ using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using Windows.Storage;
|
||||
|
||||
using static Microsoft.Plugin.Program.Programs.UWP;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
public class AppxPackageHelper
|
||||
public static class AppxPackageHelper
|
||||
{
|
||||
// This function returns a list of attributes of applications
|
||||
public List<IAppxManifestApplication> getAppsFromManifest(IStream stream)
|
||||
public static List<IAppxManifestApplication> getAppsFromManifest(IStream stream)
|
||||
{
|
||||
List<IAppxManifestApplication> apps = new List<IAppxManifestApplication>();
|
||||
var appxFactory = new AppxFactory();
|
||||
@@ -18,9 +19,9 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
var manifestApps = reader.GetApplications();
|
||||
while (manifestApps.GetHasCurrent())
|
||||
{
|
||||
string appListEntry;
|
||||
var manifestApp = manifestApps.GetCurrent();
|
||||
manifestApp.GetStringValue("AppListEntry", out appListEntry);
|
||||
var hr = manifestApp.GetStringValue("AppListEntry", out var appListEntry);
|
||||
_ = CheckHRAndReturnOrThrow(hr, appListEntry);
|
||||
if (appListEntry != "none")
|
||||
{
|
||||
apps.Add(manifestApp);
|
||||
@@ -28,55 +29,68 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
manifestApps.MoveNext();
|
||||
}
|
||||
return apps;
|
||||
}
|
||||
|
||||
public static T CheckHRAndReturnOrThrow<T>(Hresult hr, T result)
|
||||
{
|
||||
if (hr != Hresult.Ok)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR((int)hr);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reference : https://stackoverflow.com/questions/32122679/getting-icon-of-modern-windows-app-from-a-desktop-application
|
||||
[Guid("5842a140-ff9f-4166-8f5c-62f5b7b0c781"), ComImport]
|
||||
public class AppxFactory
|
||||
{
|
||||
}
|
||||
|
||||
[Guid("BEB94909-E451-438B-B5A7-D79E767B75D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxFactory
|
||||
{
|
||||
void _VtblGap0_2(); // skip 2 methods
|
||||
IAppxManifestReader CreateManifestReader(IStream inputStream);
|
||||
}
|
||||
|
||||
[Guid("4E1BD148-55A0-4480-A3D1-15544710637C"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestReader
|
||||
{
|
||||
void _VtblGap0_1(); // skip 1 method
|
||||
IAppxManifestProperties GetProperties();
|
||||
void _VtblGap1_5(); // skip 5 methods
|
||||
IAppxManifestApplicationsEnumerator GetApplications();
|
||||
}
|
||||
|
||||
[Guid("9EB8A55A-F04B-4D0D-808D-686185D4847A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestApplicationsEnumerator
|
||||
{
|
||||
IAppxManifestApplication GetCurrent();
|
||||
bool GetHasCurrent();
|
||||
bool MoveNext();
|
||||
}
|
||||
|
||||
[Guid("5DA89BF4-3773-46BE-B650-7E744863B7E8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestApplication
|
||||
{
|
||||
[PreserveSig]
|
||||
int GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value);
|
||||
|
||||
[PreserveSig]
|
||||
int GetAppUserModelId([MarshalAs(UnmanagedType.LPWStr)] out string value);
|
||||
}
|
||||
|
||||
[Guid("03FAF64D-F26F-4B2C-AAF7-8FE7789B8BCA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestProperties
|
||||
{
|
||||
[PreserveSig]
|
||||
int GetBoolValue([MarshalAs(UnmanagedType.LPWStr)] string name, out bool value);
|
||||
[PreserveSig]
|
||||
int GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value);
|
||||
}
|
||||
}
|
||||
|
||||
// Reference : https://stackoverflow.com/questions/32122679/getting-icon-of-modern-windows-app-from-a-desktop-application
|
||||
[Guid("5842a140-ff9f-4166-8f5c-62f5b7b0c781"), ComImport]
|
||||
public class AppxFactory
|
||||
{
|
||||
}
|
||||
|
||||
[Guid("BEB94909-E451-438B-B5A7-D79E767B75D8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxFactory
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")]
|
||||
void _VtblGap0_2(); // skip 2 methods
|
||||
IAppxManifestReader CreateManifestReader(IStream inputStream);
|
||||
}
|
||||
|
||||
[Guid("4E1BD148-55A0-4480-A3D1-15544710637C"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestReader
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")]
|
||||
void _VtblGap0_1(); // skip 1 method
|
||||
IAppxManifestProperties GetProperties();
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")]
|
||||
void _VtblGap1_5(); // skip 5 methods
|
||||
IAppxManifestApplicationsEnumerator GetApplications();
|
||||
}
|
||||
|
||||
[Guid("9EB8A55A-F04B-4D0D-808D-686185D4847A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestApplicationsEnumerator
|
||||
{
|
||||
IAppxManifestApplication GetCurrent();
|
||||
bool GetHasCurrent();
|
||||
bool MoveNext();
|
||||
}
|
||||
|
||||
[Guid("5DA89BF4-3773-46BE-B650-7E744863B7E8"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestApplication
|
||||
{
|
||||
[PreserveSig]
|
||||
Hresult GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value);
|
||||
|
||||
[PreserveSig]
|
||||
Hresult GetAppUserModelId([MarshalAs(UnmanagedType.LPWStr)] out string value);
|
||||
}
|
||||
|
||||
[Guid("03FAF64D-F26F-4B2C-AAF7-8FE7789B8BCA"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestProperties
|
||||
{
|
||||
[PreserveSig]
|
||||
Hresult GetBoolValue([MarshalAs(UnmanagedType.LPWStr)] string name, out bool value);
|
||||
[PreserveSig]
|
||||
Hresult GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value);
|
||||
}
|
||||
}
|
||||
|
@@ -33,6 +33,11 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
public static PackageWrapper GetWrapperFromPackage(Package package)
|
||||
{
|
||||
if(package == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(package));
|
||||
}
|
||||
|
||||
string path;
|
||||
try
|
||||
{
|
||||
|
@@ -13,7 +13,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
public class ShellLinkHelper : IShellLinkHelper
|
||||
{
|
||||
[Flags()]
|
||||
public enum SLGP_FLAGS
|
||||
private enum SLGP_FLAGS
|
||||
{
|
||||
SLGP_SHORTPATH = 0x1,
|
||||
SLGP_UNCPRIORITY = 0x2,
|
||||
@@ -21,7 +21,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public struct WIN32_FIND_DATAW
|
||||
private struct WIN32_FIND_DATAW
|
||||
{
|
||||
public uint dwFileAttributes;
|
||||
public long ftCreationTime;
|
||||
@@ -37,7 +37,8 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
public string cAlternateFileName;
|
||||
}
|
||||
|
||||
[Flags()]
|
||||
[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,
|
||||
@@ -96,7 +97,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
}
|
||||
|
||||
[ComImport(), Guid("00021401-0000-0000-C000-000000000046")]
|
||||
public class ShellLink
|
||||
private class ShellLink
|
||||
{
|
||||
}
|
||||
|
||||
|
@@ -1,40 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Management.Deployment;
|
||||
using Wox.Infrastructure;
|
||||
using Microsoft.Plugin.Program.Logger;
|
||||
using Rect = System.Windows.Rect;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Wox.Plugin;
|
||||
using System.Windows.Input;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using Wox.Plugin.SharedCommands;
|
||||
using System.Reflection;
|
||||
using Wox.Infrastructure.Image;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Microsoft.Plugin.Program.Win32;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
[Serializable]
|
||||
public class UWP
|
||||
public partial class UWP
|
||||
{
|
||||
public string Name { get; }
|
||||
public string FullName { get; }
|
||||
public string FamilyName { get; }
|
||||
public string Location { get; set; }
|
||||
|
||||
public Application[] Apps { get; set; }
|
||||
public IList<UWPApplication> Apps { get; private set; }
|
||||
|
||||
public PackageVersion Version { get; set; }
|
||||
|
||||
@@ -42,6 +33,11 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
public UWP(IPackage package)
|
||||
{
|
||||
if (package == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(package));
|
||||
}
|
||||
|
||||
Name = package.Name;
|
||||
FullName = package.FullName;
|
||||
FamilyName = package.FamilyName;
|
||||
@@ -50,7 +46,6 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
public void InitializeAppInfo(string installedLocation)
|
||||
{
|
||||
Location = installedLocation;
|
||||
AppxPackageHelper _helper = new AppxPackageHelper();
|
||||
var path = Path.Combine(installedLocation, "AppxManifest.xml");
|
||||
|
||||
var namespaces = XmlNamespaces(path);
|
||||
@@ -59,16 +54,16 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
IStream stream;
|
||||
const uint noAttribute = 0x80;
|
||||
const Stgm exclusiveRead = Stgm.Read;
|
||||
var hResult = SHCreateStreamOnFileEx(path, exclusiveRead, noAttribute, false, null, out stream);
|
||||
var hResult = NativeMethods.SHCreateStreamOnFileEx(path, exclusiveRead, noAttribute, false, null, out stream);
|
||||
|
||||
if (hResult == Hresult.Ok)
|
||||
{
|
||||
var apps = new List<Application>();
|
||||
var apps = new List<UWPApplication>();
|
||||
|
||||
List<AppxPackageHelper.IAppxManifestApplication> _apps = _helper.getAppsFromManifest(stream);
|
||||
List<IAppxManifestApplication> _apps = AppxPackageHelper.getAppsFromManifest(stream);
|
||||
foreach (var _app in _apps)
|
||||
{
|
||||
var app = new Application(_app, this);
|
||||
var app = new UWPApplication(_app, this);
|
||||
apps.Add(app);
|
||||
}
|
||||
|
||||
@@ -88,14 +83,14 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
ProgramLogger.LogException($"|UWP|InitializeAppInfo|{path}" +
|
||||
"|Error caused while trying to get the details of the UWP program", e);
|
||||
|
||||
Apps = new List<Application>().ToArray();
|
||||
Apps = new List<UWPApplication>().ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx
|
||||
private string[] XmlNamespaces(string path)
|
||||
private static string[] XmlNamespaces(string path)
|
||||
{
|
||||
XDocument z = XDocument.Load(path);
|
||||
if (z.Root != null)
|
||||
@@ -112,10 +107,9 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
}
|
||||
else
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|XmlNamespaces|{path}" +
|
||||
$"|Error occurred while trying to get the XML from {path}", new ArgumentNullException());
|
||||
Log.Error($"|UWP.XmlNamespaces|Error occurred while trying to get the XML from {path}");
|
||||
|
||||
return new string[] { };
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,9 +136,10 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
+ $"{FullName} from location {Location} is returned.", new FormatException());
|
||||
|
||||
Version = PackageVersion.Unknown;
|
||||
}
|
||||
|
||||
public static Application[] All()
|
||||
}
|
||||
|
||||
[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);
|
||||
var support = Environment.OSVersion.Version.Major >= windows10.Major;
|
||||
@@ -162,7 +157,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
ProgramLogger.LogException($"|UWP|All|{p.InstalledLocation}|An unexpected error occurred and "
|
||||
+ $"unable to convert Package to UWP for {p.FullName}", e);
|
||||
return new Application[] { };
|
||||
return Array.Empty<UWPApplication>();
|
||||
}
|
||||
return u.Apps;
|
||||
}).ToArray();
|
||||
@@ -176,10 +171,11 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Application[] { };
|
||||
return Array.Empty<UWPApplication>();
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Intentially keeping the process alive.")]
|
||||
private static IEnumerable<IPackage> CurrentUserPackages()
|
||||
{
|
||||
var ps = PackageManagerWrapper.FindPackagesForCurrentUser();
|
||||
@@ -215,7 +211,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
if (obj is UWP uwp)
|
||||
{
|
||||
return FamilyName.Equals(uwp.FamilyName);
|
||||
return FamilyName.Equals(uwp.FamilyName, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -225,452 +221,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return FamilyName.GetHashCode();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Application : 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 LogoUri { get; set; }
|
||||
public string LogoPath { get; set; }
|
||||
public UWP Package { get; set; }
|
||||
|
||||
// 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 string SetSubtitle(IPublicAPI api)
|
||||
{
|
||||
return api.GetTranslation("powertoys_run_plugin_program_packaged_application");
|
||||
}
|
||||
|
||||
public Result Result(string query, IPublicAPI 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("{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_name"), result.Title);
|
||||
var toolTipText = string.Format("{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_path"), Package.Location);
|
||||
result.ToolTipData = new ToolTipData(toolTipTitle, toolTipText);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<ContextMenuResult> ContextMenus(IPublicAPI 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.Trim();
|
||||
command = Environment.ExpandEnvironmentVariables(command);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
private async void Launch(IPublicAPI api)
|
||||
{
|
||||
var appManager = new ApplicationActivationHelper.ApplicationActivationManager();
|
||||
uint unusedPid;
|
||||
const string noArgs = "";
|
||||
const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None;
|
||||
await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
appManager.ActivateApplication(UserModelId, noArgs, noFlags, out unusedPid);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
var name = "Plugin: Program";
|
||||
var message = $"Can't start UWP: {DisplayName}";
|
||||
api.ShowMsg(name, message, string.Empty);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Application(AppxPackageHelper.IAppxManifestApplication manifestApp, UWP package)
|
||||
{
|
||||
// This is done because we cannot use the keyword 'out' along with a property
|
||||
string tmpUserModelId;
|
||||
string tmpUniqueIdentifier;
|
||||
string tmpDisplayName;
|
||||
string tmpDescription;
|
||||
string tmpBackgroundColor;
|
||||
string tmpEntryPoint;
|
||||
|
||||
manifestApp.GetAppUserModelId(out tmpUserModelId);
|
||||
manifestApp.GetAppUserModelId(out tmpUniqueIdentifier);
|
||||
manifestApp.GetStringValue("DisplayName", out tmpDisplayName);
|
||||
manifestApp.GetStringValue("Description", out tmpDescription);
|
||||
manifestApp.GetStringValue("BackgroundColor", out tmpBackgroundColor);
|
||||
manifestApp.GetStringValue("EntryPoint", out tmpEntryPoint);
|
||||
|
||||
UserModelId = tmpUserModelId;
|
||||
UniqueIdentifier = tmpUniqueIdentifier;
|
||||
DisplayName = tmpDisplayName;
|
||||
Description = tmpDescription;
|
||||
BackgroundColor = tmpBackgroundColor;
|
||||
EntryPoint = tmpEntryPoint;
|
||||
|
||||
Package = 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.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
internal string ResourceFromPri(string packageFullName, string resourceReference)
|
||||
{
|
||||
const string prefix = "ms-resource:";
|
||||
if (!string.IsNullOrWhiteSpace(resourceReference) && resourceReference.StartsWith(prefix))
|
||||
{
|
||||
// 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("//"))
|
||||
{
|
||||
parsed = prefix + key;
|
||||
}
|
||||
else if (key.StartsWith("/"))
|
||||
{
|
||||
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 = 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(AppxPackageHelper.IAppxManifestApplication app)
|
||||
{
|
||||
var logoKeyFromVersion = new Dictionary<PackageVersion, string>
|
||||
{
|
||||
{ PackageVersion.Windows10, "Square44x44Logo" },
|
||||
{ PackageVersion.Windows81, "Square30x30Logo" },
|
||||
{ PackageVersion.Windows8, "SmallLogo" },
|
||||
};
|
||||
if (logoKeyFromVersion.ContainsKey(Package.Version))
|
||||
{
|
||||
string logoUri;
|
||||
var key = logoKeyFromVersion[Package.Version];
|
||||
app.GetStringValue(key, out 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("\\"))
|
||||
{
|
||||
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)).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)).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}";
|
||||
}
|
||||
return FamilyName.GetHashCode(StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public enum PackageVersion
|
||||
@@ -681,23 +232,18 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
Unknown
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum Stgm : uint
|
||||
[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 : Int64
|
||||
{
|
||||
Read = 0x0,
|
||||
Read = 0x00000000L,
|
||||
}
|
||||
|
||||
private enum Hresult : uint
|
||||
public enum Hresult : Int32
|
||||
{
|
||||
Ok = 0x0000,
|
||||
}
|
||||
Ok = 0x0,
|
||||
}
|
||||
|
||||
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern Hresult SHCreateStreamOnFileEx(string fileName, Stgm grfMode, uint attributes, bool create,
|
||||
IStream reserved, out IStream stream);
|
||||
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf,
|
||||
IntPtr ppvReserved);
|
||||
}
|
||||
}
|
@@ -0,0 +1,486 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Wox.Infrastructure;
|
||||
using Microsoft.Plugin.Program.Logger;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Wox.Plugin;
|
||||
using System.Windows.Input;
|
||||
using Wox.Plugin.SharedCommands;
|
||||
using System.Reflection;
|
||||
using Wox.Infrastructure.Image;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using static Microsoft.Plugin.Program.Programs.UWP;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Globalization;
|
||||
using Microsoft.Plugin.Program.Win32;
|
||||
|
||||
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();
|
||||
uint unusedPid;
|
||||
const string noArgs = "";
|
||||
const ApplicationActivationHelper.ActivateOptions noFlags = ApplicationActivationHelper.ActivateOptions.None;
|
||||
await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
appManager.ActivateApplication(UserModelId, noArgs, noFlags, out 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);
|
||||
this.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(this.logoUri, "contrast-white");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogoPath = LogoPathFromUri(this.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}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -16,12 +16,13 @@ using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.FileSystemHelper;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
|
||||
[Serializable]
|
||||
public class Win32 : IProgram
|
||||
public class Win32Program : IProgram
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string UniqueIdentifier { get; set; }
|
||||
@@ -38,9 +39,9 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
public string Location => ParentDirectory;
|
||||
public uint AppType { get; set; }
|
||||
// Wrappers for File Operations
|
||||
public static IFileVersionInfoWrapper _fileVersionInfoWrapper = new FileVersionInfoWrapper();
|
||||
public static IFileWrapper _fileWrapper = new FileWrapper();
|
||||
public static IShellLinkHelper _helper = new ShellLinkHelper();
|
||||
public static IFileVersionInfoWrapper FileVersionInfoWrapper { get; set;} = new FileVersionInfoWrapper();
|
||||
public static IFileWrapper FileWrapper { get; set; } = new FileWrapper();
|
||||
public static IShellLinkHelper Helper { get; set; } = new ShellLinkHelper();
|
||||
|
||||
private const string ShortcutExtension = "lnk";
|
||||
private const string ApplicationReferenceExtension = "appref-ms";
|
||||
@@ -73,7 +74,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
// To Filter PWAs when the user searches for the main application
|
||||
// All Chromium based applications contain the --app-id argument
|
||||
// Reference : https://codereview.chromium.org/399045/show
|
||||
bool isWebApplication = FullPath.Contains(proxyWebApp) && Arguments.Contains(appIdArgument);
|
||||
bool isWebApplication = FullPath.Contains(proxyWebApp, StringComparison.OrdinalIgnoreCase) && Arguments.Contains(appIdArgument, StringComparison.OrdinalIgnoreCase);
|
||||
return isWebApplication;
|
||||
}
|
||||
|
||||
@@ -89,7 +90,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
// Set the subtitle to 'Web Application'
|
||||
AppType = (uint)ApplicationTypes.WEB_APPLICATION;
|
||||
|
||||
string[] subqueries = query.Split();
|
||||
string[] subqueries = query?.Split() ?? Array.Empty<string>();
|
||||
bool nameContainsQuery = false;
|
||||
bool pathContainsQuery = false;
|
||||
|
||||
@@ -136,7 +137,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
public bool QueryEqualsNameForRunCommands(string query)
|
||||
{
|
||||
if (AppType == (uint)ApplicationTypes.RUN_COMMAND
|
||||
if (query != null && AppType == (uint)ApplicationTypes.RUN_COMMAND
|
||||
&& !query.Equals(Name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
@@ -147,6 +148,11 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
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 this is zero
|
||||
@@ -198,16 +204,21 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
result.Title = Name;
|
||||
result.TitleHighlightData = StringMatcher.FuzzySearch(query, Name).MatchData;
|
||||
|
||||
var toolTipTitle = string.Format("{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_name"), result.Title);
|
||||
var toolTipText = string.Format("{0}: {1}", api.GetTranslation("powertoys_run_plugin_program_file_path"), FullPath);
|
||||
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"), FullPath);
|
||||
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 (AppType != (uint)ApplicationTypes.INTERNET_SHORTCUT_APPLICATION)
|
||||
@@ -266,7 +277,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
{
|
||||
try
|
||||
{
|
||||
Helper.OpenInConsole(ParentDirectory);
|
||||
Wox.Infrastructure.Helper.OpenInConsole(ParentDirectory);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -287,16 +298,16 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
return ExecutableName;
|
||||
}
|
||||
|
||||
private static Win32 Win32Program(string path)
|
||||
private static Win32Program CreateWin32Program(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var p = new Win32
|
||||
var p = new Win32Program
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(path),
|
||||
ExecutableName = Path.GetFileName(path),
|
||||
IcoPath = path,
|
||||
FullPath = path.ToLower(),
|
||||
FullPath = path.ToLower(CultureInfo.CurrentCulture),
|
||||
UniqueIdentifier = path,
|
||||
ParentDirectory = Directory.GetParent(path).FullName,
|
||||
Description = string.Empty,
|
||||
@@ -311,14 +322,14 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
ProgramLogger.LogException($"|Win32|Win32Program|{path}" +
|
||||
$"|Permission denied when trying to load the program from {path}", e);
|
||||
|
||||
return new Win32() { Valid = false, Enabled = false };
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
}
|
||||
|
||||
// This function filters Internet Shortcut programs
|
||||
private static Win32 InternetShortcutProgram(string path)
|
||||
private static Win32Program InternetShortcutProgram(string path)
|
||||
{
|
||||
string[] lines = _fileWrapper.ReadAllLines(path);
|
||||
string[] lines = FileWrapper.ReadAllLines(path);
|
||||
string appName = string.Empty;
|
||||
string iconPath = string.Empty;
|
||||
string urlPath = string.Empty;
|
||||
@@ -332,7 +343,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
foreach (string line in lines)
|
||||
{
|
||||
if (line.StartsWith(urlPrefix))
|
||||
if (line.StartsWith(urlPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
urlPath = line.Substring(urlPrefix.Length);
|
||||
Uri uri = new Uri(urlPath);
|
||||
@@ -344,7 +355,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
}
|
||||
}
|
||||
|
||||
if (line.StartsWith(iconFilePrefix))
|
||||
if (line.StartsWith(iconFilePrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
iconPath = line.Substring(iconFilePrefix.Length);
|
||||
}
|
||||
@@ -352,12 +363,12 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
if (!validApp)
|
||||
{
|
||||
return new Win32() { Valid = false, Enabled = false };
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var p = new Win32
|
||||
var p = new Win32Program
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(path),
|
||||
ExecutableName = Path.GetFileName(path),
|
||||
@@ -376,19 +387,20 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
ProgramLogger.LogException($"|Win32|InternetShortcutProgram|{path}" +
|
||||
$"|Permission denied when trying to load the program from {path}", e);
|
||||
|
||||
return new Win32() { Valid = false, Enabled = false };
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
}
|
||||
|
||||
private static Win32 LnkProgram(string path)
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Unsure of what exceptions are caught here while enabling static analysis")]
|
||||
private static Win32Program LnkProgram(string path)
|
||||
{
|
||||
var program = Win32Program(path);
|
||||
var program = CreateWin32Program(path);
|
||||
try
|
||||
{
|
||||
const int MAX_PATH = 260;
|
||||
StringBuilder buffer = new StringBuilder(MAX_PATH);
|
||||
|
||||
string target = _helper.RetrieveTargetPath(path);
|
||||
string target = Helper.RetrieveTargetPath(path);
|
||||
|
||||
if (!string.IsNullOrEmpty(target))
|
||||
{
|
||||
@@ -396,19 +408,19 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
if (extension == ExeExtension && File.Exists(target))
|
||||
{
|
||||
program.LnkResolvedPath = program.FullPath;
|
||||
program.FullPath = Path.GetFullPath(target).ToLower();
|
||||
program.FullPath = Path.GetFullPath(target).ToLower(CultureInfo.CurrentCulture);
|
||||
program.ExecutableName = Path.GetFileName(target);
|
||||
program.hasArguments = _helper.hasArguments;
|
||||
program.Arguments = _helper.Arguments;
|
||||
program.hasArguments = Helper.hasArguments;
|
||||
program.Arguments = Helper.Arguments;
|
||||
|
||||
var description = _helper.description;
|
||||
var description = Helper.description;
|
||||
if (!string.IsNullOrEmpty(description))
|
||||
{
|
||||
program.Description = description;
|
||||
}
|
||||
else
|
||||
{
|
||||
var info = _fileVersionInfoWrapper.GetVersionInfo(target);
|
||||
var info = FileVersionInfoWrapper.GetVersionInfo(target);
|
||||
if (!string.IsNullOrEmpty(info?.FileDescription))
|
||||
{
|
||||
program.Description = info.FileDescription;
|
||||
@@ -418,16 +430,8 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
}
|
||||
return program;
|
||||
}
|
||||
catch (COMException e)
|
||||
{
|
||||
// C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\MiracastView.lnk always cause exception
|
||||
ProgramLogger.LogException($"|Win32|LnkProgram|{path}" +
|
||||
"|Error caused likely due to trying to get the description of the program", e);
|
||||
|
||||
program.Valid = false;
|
||||
return program;
|
||||
}
|
||||
#if !DEBUG //Only do a catch all in production. This is so make developer aware of any unhandled exception and add the exception handling in.
|
||||
//Only do a catch all in production. This is so make developer aware of any unhandled exception and add the exception handling in.
|
||||
//Error caused likely due to trying to get the description of the program
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.LogException($"|Win32|LnkProgram|{path}" +
|
||||
@@ -436,15 +440,14 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
program.Valid = false;
|
||||
return program;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static Win32 ExeProgram(string path)
|
||||
private static Win32Program ExeProgram(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var program = Win32Program(path);
|
||||
var info = _fileVersionInfoWrapper.GetVersionInfo(path);
|
||||
var program = CreateWin32Program(path);
|
||||
var info = FileVersionInfoWrapper.GetVersionInfo(path);
|
||||
|
||||
if (!string.IsNullOrEmpty(info?.FileDescription))
|
||||
{
|
||||
@@ -458,14 +461,19 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
ProgramLogger.LogException($"|Win32|ExeProgram|{path}" +
|
||||
$"|Permission denied when trying to load the program from {path}", e);
|
||||
|
||||
return new Win32() { Valid = false, Enabled = false };
|
||||
return new Win32Program() { Valid = false, Enabled = false };
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get the Win32 application, given the path to the application
|
||||
public static Win32 GetAppFromPath(string path)
|
||||
public static Win32Program GetAppFromPath(string path)
|
||||
{
|
||||
Win32 app = null;
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
|
||||
Win32Program app = null;
|
||||
const string exeExtension = ".exe";
|
||||
const string lnkExtension = ".lnk";
|
||||
const string urlExtenion = ".url";
|
||||
@@ -483,7 +491,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
}
|
||||
else if (extension.Equals(apprefExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
app = Win32Program(path);
|
||||
app = CreateWin32Program(path);
|
||||
}
|
||||
else if (extension.Equals(urlExtenion, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -502,11 +510,11 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ProgramPaths(string directory, string[] suffixes, bool recursiveSearch = true)
|
||||
private static IEnumerable<string> ProgramPaths(string directory, IList<string> suffixes, bool recursiveSearch = true)
|
||||
{
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
return new string[] { };
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
var files = new List<string>();
|
||||
@@ -562,7 +570,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
private static string Extension(string path)
|
||||
{
|
||||
var extension = Path.GetExtension(path)?.ToLower();
|
||||
var extension = Path.GetExtension(path)?.ToLower(CultureInfo.CurrentCulture);
|
||||
|
||||
if (!string.IsNullOrEmpty(extension))
|
||||
{
|
||||
@@ -574,7 +582,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
}
|
||||
}
|
||||
|
||||
private static ParallelQuery<Win32> UnregisteredPrograms(List<Settings.ProgramSource> sources, string[] suffixes)
|
||||
private static ParallelQuery<Win32Program> UnregisteredPrograms(List<ProgramSource> sources, IList<string> suffixes)
|
||||
{
|
||||
var listToAdd = new List<string>();
|
||||
sources.Where(s => Directory.Exists(s.Location) && s.Enabled)
|
||||
@@ -591,13 +599,13 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
var programs3 = from p in paths.AsParallel()
|
||||
let e = Extension(p)
|
||||
where e != ShortcutExtension && e != ExeExtension
|
||||
select Win32Program(p);
|
||||
select CreateWin32Program(p);
|
||||
return programs1.Concat(programs2).Concat(programs3);
|
||||
}
|
||||
|
||||
|
||||
// Function to obtain the list of applications, the locations of which have been added to the env variable PATH
|
||||
private static ParallelQuery<Win32> PathEnvironmentPrograms(string[] suffixes)
|
||||
private static ParallelQuery<Win32Program> PathEnvironmentPrograms(IList<string> suffixes)
|
||||
{
|
||||
|
||||
// To get all the locations stored in the PATH env variable
|
||||
@@ -622,7 +630,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
.ToArray();
|
||||
|
||||
var programs1 = allPaths.AsParallel().Where(p => Extension(p).Equals(ShortcutExtension, StringComparison.OrdinalIgnoreCase)).Select(LnkProgram);
|
||||
var programs2 = allPaths.AsParallel().Where(p => Extension(p).Equals(ApplicationReferenceExtension, StringComparison.OrdinalIgnoreCase)).Select(Win32Program);
|
||||
var programs2 = allPaths.AsParallel().Where(p => Extension(p).Equals(ApplicationReferenceExtension, StringComparison.OrdinalIgnoreCase)).Select(CreateWin32Program);
|
||||
var programs3 = allPaths.AsParallel().Where(p => Extension(p).Equals(InternetShortcutExtension, StringComparison.OrdinalIgnoreCase)).Select(InternetShortcutProgram);
|
||||
var programs4 = allPaths.AsParallel().Where(p => Extension(p).Equals(ExeExtension, StringComparison.OrdinalIgnoreCase)).Select(ExeProgram);
|
||||
|
||||
@@ -634,7 +642,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
return allPrograms;
|
||||
}
|
||||
|
||||
private static ParallelQuery<Win32> IndexPath(string[] suffixes, List<string> IndexLocation)
|
||||
private static ParallelQuery<Win32Program> IndexPath(IList<string> suffixes, List<string> IndexLocation)
|
||||
{
|
||||
var disabledProgramsList = Main._settings.DisabledProgramSources;
|
||||
|
||||
@@ -652,7 +660,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
.ToArray();
|
||||
|
||||
var programs1 = paths.AsParallel().Where(p => Extension(p).Equals(ShortcutExtension, StringComparison.OrdinalIgnoreCase)).Select(LnkProgram);
|
||||
var programs2 = paths.AsParallel().Where(p => Extension(p).Equals(ApplicationReferenceExtension, StringComparison.OrdinalIgnoreCase)).Select(Win32Program);
|
||||
var programs2 = paths.AsParallel().Where(p => Extension(p).Equals(ApplicationReferenceExtension, StringComparison.OrdinalIgnoreCase)).Select(CreateWin32Program);
|
||||
var programs3 = paths.AsParallel().Where(p => Extension(p).Equals(InternetShortcutExtension, StringComparison.OrdinalIgnoreCase)).Select(InternetShortcutProgram);
|
||||
var programs4 = paths.AsParallel().Where(p => Extension(p).Equals(ExeExtension, StringComparison.OrdinalIgnoreCase)).Select(ExeProgram);
|
||||
|
||||
@@ -661,7 +669,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
.Concat(programs4).Where(p => p.Valid);
|
||||
}
|
||||
|
||||
private static ParallelQuery<Win32> StartMenuPrograms(string[] suffixes)
|
||||
private static ParallelQuery<Win32Program> StartMenuPrograms(IList<string> suffixes)
|
||||
{
|
||||
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Programs);
|
||||
var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonPrograms);
|
||||
@@ -670,7 +678,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
return IndexPath(suffixes, IndexLocation);
|
||||
}
|
||||
|
||||
private static ParallelQuery<Win32> DesktopPrograms(string[] suffixes)
|
||||
private static ParallelQuery<Win32Program> DesktopPrograms(IList<string> suffixes)
|
||||
{
|
||||
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||
List<string> IndexLocation = new List<string>() { directory1 };
|
||||
@@ -678,11 +686,11 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
return IndexPath(suffixes, IndexLocation);
|
||||
}
|
||||
|
||||
private static ParallelQuery<Win32> AppPathsPrograms(string[] suffixes)
|
||||
private static ParallelQuery<Win32Program> AppPathsPrograms(IList<string> suffixes)
|
||||
{
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ee872121
|
||||
const string appPaths = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths";
|
||||
var programs = new List<Win32>();
|
||||
var programs = new List<Win32Program>();
|
||||
using (var root = Registry.LocalMachine.OpenSubKey(appPaths))
|
||||
{
|
||||
if (root != null)
|
||||
@@ -706,7 +714,7 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
return filtered;
|
||||
}
|
||||
|
||||
private static IEnumerable<Win32> GetProgramsFromRegistry(RegistryKey root)
|
||||
private static IEnumerable<Win32Program> GetProgramsFromRegistry(RegistryKey root)
|
||||
{
|
||||
return root
|
||||
.GetSubKeyNames()
|
||||
@@ -744,17 +752,17 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
}
|
||||
}
|
||||
|
||||
private static Win32 GetProgramFromPath(string path)
|
||||
private static Win32Program GetProgramFromPath(string path)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return new Win32();
|
||||
return new Win32Program();
|
||||
|
||||
path = Environment.ExpandEnvironmentVariables(path);
|
||||
|
||||
if (!File.Exists(path))
|
||||
return new Win32();
|
||||
return new Win32Program();
|
||||
|
||||
var entry = Win32Program(path);
|
||||
var entry = CreateWin32Program(path);
|
||||
entry.ExecutableName = Path.GetFileName(path);
|
||||
|
||||
return entry;
|
||||
@@ -770,12 +778,12 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
removeDuplicatesComparer _removeDuplicatesHelper = new removeDuplicatesComparer();
|
||||
return obj is Win32 win && _removeDuplicatesHelper.Equals(this, win);
|
||||
return obj is Win32Program win && _removeDuplicatesHelper.Equals(this, win);
|
||||
}
|
||||
|
||||
public class removeDuplicatesComparer : IEqualityComparer<Win32>
|
||||
private class removeDuplicatesComparer : IEqualityComparer<Win32Program>
|
||||
{
|
||||
public bool Equals(Win32 app1, Win32 app2)
|
||||
public bool Equals(Win32Program app1, Win32Program app2)
|
||||
{
|
||||
|
||||
if (!string.IsNullOrEmpty(app1.Name) && !string.IsNullOrEmpty(app2.Name)
|
||||
@@ -790,34 +798,40 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
}
|
||||
|
||||
// Ref : https://stackoverflow.com/questions/2730865/how-do-i-calculate-a-good-hash-code-for-a-list-of-strings
|
||||
public int GetHashCode(Win32 obj)
|
||||
public int GetHashCode(Win32Program obj)
|
||||
{
|
||||
int namePrime = 13;
|
||||
int executablePrime = 17;
|
||||
int fullPathPrime = 31;
|
||||
|
||||
int result = 1;
|
||||
result = result * namePrime + obj.Name.ToLowerInvariant().GetHashCode();
|
||||
result = result * executablePrime + obj.ExecutableName.ToLowerInvariant().GetHashCode();
|
||||
result = result * fullPathPrime + obj.FullPath.ToLowerInvariant().GetHashCode();
|
||||
result = result * namePrime + obj.Name.ToUpperInvariant().GetHashCode(StringComparison.Ordinal);
|
||||
result = result * executablePrime + obj.ExecutableName.ToUpperInvariant().GetHashCode(StringComparison.Ordinal);
|
||||
result = result * fullPathPrime + obj.FullPath.ToUpperInvariant().GetHashCode(StringComparison.Ordinal);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplication code
|
||||
public static Func<ParallelQuery<Win32>, Win32[]> DeduplicatePrograms = (programs) =>
|
||||
public static Win32Program[] DeduplicatePrograms(ParallelQuery<Win32Program> programs)
|
||||
{
|
||||
var uniqueExePrograms = programs.Where(x => !(string.IsNullOrEmpty(x.LnkResolvedPath) && (Extension(x.FullPath) == ExeExtension) && !(x.AppType == (uint)ApplicationTypes.RUN_COMMAND)));
|
||||
var uniquePrograms = uniqueExePrograms.Distinct(new removeDuplicatesComparer());
|
||||
return uniquePrograms.ToArray();
|
||||
};
|
||||
|
||||
public static Win32[] All(Settings settings)
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "Keeping the process alive but logging the exception")]
|
||||
public static Win32Program[] All(ProgramPluginSettings settings)
|
||||
{
|
||||
if (settings == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(settings));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var programs = new List<Win32>().AsParallel();
|
||||
var programs = new List<Win32Program>().AsParallel();
|
||||
|
||||
var unregistered = UnregisteredPrograms(settings.ProgramSources, settings.ProgramSuffixes);
|
||||
programs = programs.Concat(unregistered);
|
||||
@@ -848,21 +862,12 @@ namespace Microsoft.Plugin.Program.Programs
|
||||
|
||||
return DeduplicatePrograms(programs);
|
||||
}
|
||||
#if DEBUG //This is to make developer aware of any unhandled exception and add in handling.
|
||||
catch (Exception e)
|
||||
{
|
||||
throw e;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !DEBUG //Only do a catch all in production.
|
||||
catch (Exception e)
|
||||
{
|
||||
ProgramLogger.LogException("|Win32|All|Not available|An unexpected error occurred", e);
|
||||
|
||||
return new Win32[0];
|
||||
return Array.Empty<Win32Program>();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.Plugin.Program
|
||||
{
|
||||
public class Settings
|
||||
{
|
||||
public DateTime LastIndexTime { get; set; }
|
||||
public List<ProgramSource> ProgramSources { get; set; } = new List<ProgramSource>();
|
||||
public List<DisabledProgramSource> DisabledProgramSources { get; set; } = new List<DisabledProgramSource>();
|
||||
public string[] ProgramSuffixes { get; set; } = { "bat", "appref-ms", "exe", "lnk", "url" };
|
||||
|
||||
public bool EnableStartMenuSource { get; set; } = true;
|
||||
|
||||
public bool EnableDesktopSource { get; set; } = true;
|
||||
|
||||
public bool EnableRegistrySource { get; set; } = true;
|
||||
|
||||
public bool EnablePathEnvironmentVariableSource { get; set; } = true;
|
||||
|
||||
public double MinScoreThreshold { get; set; } = 0.75;
|
||||
|
||||
internal const char SuffixSeparator = ';';
|
||||
|
||||
/// <summary>
|
||||
/// Contains user added folder location contents as well as all user disabled applications
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Win32 class applications set UniqueIdentifier using their full file path</para>
|
||||
/// <para>UWP class applications set UniqueIdentifier using their Application User Model ID</para>
|
||||
/// <para>Custom user added program sources set UniqueIdentifier using their location</para>
|
||||
/// </remarks>
|
||||
public class ProgramSource
|
||||
{
|
||||
private string name;
|
||||
|
||||
public string Location { get; set; }
|
||||
public string Name { get => name ?? new DirectoryInfo(Location).Name; set => name = value; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
public string UniqueIdentifier { get; set; }
|
||||
}
|
||||
|
||||
public class DisabledProgramSource : ProgramSource { }
|
||||
}
|
||||
}
|
@@ -14,15 +14,15 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
/// 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<UWP.Application>, IProgramRepository
|
||||
internal class PackageRepository : ListRepository<UWPApplication>, IProgramRepository
|
||||
{
|
||||
private IStorage<IList<UWP.Application>> _storage;
|
||||
private IStorage<IList<UWPApplication>> _storage;
|
||||
|
||||
private IPackageCatalog _packageCatalog;
|
||||
public PackageRepository(IPackageCatalog packageCatalog, IStorage<IList<UWP.Application>> storage)
|
||||
public PackageRepository(IPackageCatalog packageCatalog, IStorage<IList<UWPApplication>> storage)
|
||||
{
|
||||
_storage = storage ?? throw new ArgumentNullException("storage", "StorageRepository requires an initialized storage interface");
|
||||
_packageCatalog = packageCatalog ?? throw new ArgumentNullException("packageCatalog", "PackageRepository expects an interface to be able to subscribe to package events");
|
||||
_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;
|
||||
}
|
||||
@@ -76,7 +76,7 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
var windows10 = new Version(10, 0);
|
||||
var support = Environment.OSVersion.Version.Major >= windows10.Major;
|
||||
|
||||
var applications = support ? Programs.UWP.All() : new Programs.UWP.Application[] { };
|
||||
var applications = support ? Programs.UWP.All() : Array.Empty<UWPApplication>();
|
||||
Set(applications);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
|
||||
public void Load()
|
||||
{
|
||||
var items = _storage.TryLoad(new Programs.UWP.Application[] { });
|
||||
var items = _storage.TryLoad(Array.Empty<UWPApplication>());
|
||||
Set(items);
|
||||
}
|
||||
}
|
||||
|
@@ -19,7 +19,7 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
}
|
||||
|
||||
// Returns an array of paths to be watched
|
||||
private string[] GetPathsToWatch()
|
||||
private static string[] GetPathsToWatch()
|
||||
{
|
||||
string[] paths = new string[]
|
||||
{
|
||||
|
@@ -6,14 +6,15 @@ using Wox.Infrastructure.Logger;
|
||||
using Wox.Infrastructure.Storage;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Storage
|
||||
{
|
||||
using Win32 = Programs.Win32;
|
||||
internal class Win32ProgramRepository : ListRepository<Programs.Win32>, IProgramRepository
|
||||
using Win32Program = Programs.Win32Program;
|
||||
internal class Win32ProgramRepository : ListRepository<Programs.Win32Program>, IProgramRepository
|
||||
{
|
||||
private IStorage<IList<Programs.Win32>> _storage;
|
||||
private Settings _settings;
|
||||
private IStorage<IList<Programs.Win32Program>> _storage;
|
||||
private ProgramPluginSettings _settings;
|
||||
private IList<IFileSystemWatcherWrapper> _fileSystemWatcherHelpers;
|
||||
private string[] _pathsToWatch;
|
||||
private int _numberOfPathsToWatch;
|
||||
@@ -21,13 +22,13 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
private readonly string lnkExtension = ".lnk";
|
||||
private readonly string urlExtension = ".url";
|
||||
|
||||
public Win32ProgramRepository(IList<IFileSystemWatcherWrapper> fileSystemWatcherHelpers, IStorage<IList<Win32>> storage, Settings settings, string[] pathsToWatch)
|
||||
public Win32ProgramRepository(IList<IFileSystemWatcherWrapper> fileSystemWatcherHelpers, IStorage<IList<Win32Program>> storage, ProgramPluginSettings settings, string[] pathsToWatch)
|
||||
{
|
||||
this._fileSystemWatcherHelpers = fileSystemWatcherHelpers;
|
||||
this._storage = storage ?? throw new ArgumentNullException("storage", "Win32ProgramRepository requires an initialized storage interface");
|
||||
this._settings = settings ?? throw new ArgumentNullException("settings", "Win32ProgramRepository requires an initialized settings object");
|
||||
this._storage = storage ?? throw new ArgumentNullException(nameof(storage), "Win32ProgramRepository requires an initialized storage interface");
|
||||
this._settings = settings ?? throw new ArgumentNullException(nameof(settings), "Win32ProgramRepository requires an initialized settings object");
|
||||
this._pathsToWatch = pathsToWatch;
|
||||
this._numberOfPathsToWatch = pathsToWatch.Count();
|
||||
this._numberOfPathsToWatch = pathsToWatch.Length;
|
||||
InitializeFileSystemWatchers();
|
||||
}
|
||||
|
||||
@@ -56,16 +57,17 @@ 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>")]
|
||||
private void OnAppRenamed(object sender, RenamedEventArgs e)
|
||||
{
|
||||
string oldPath = e.OldFullPath;
|
||||
string newPath = e.FullPath;
|
||||
|
||||
string extension = Path.GetExtension(newPath);
|
||||
Programs.Win32 newApp = Programs.Win32.GetAppFromPath(newPath);
|
||||
Programs.Win32 oldApp = null;
|
||||
Programs.Win32Program newApp = Programs.Win32Program.GetAppFromPath(newPath);
|
||||
Programs.Win32Program oldApp = null;
|
||||
|
||||
// Once the shortcut application is renamed, the old app does not exist and therefore when we try to get the FullPath we get the lnk path instead of the exe path
|
||||
// This changes the hashCode() of the old application.
|
||||
@@ -75,15 +77,15 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
{
|
||||
if (extension.Equals(lnkExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
oldApp = new Win32() { Name = Path.GetFileNameWithoutExtension(e.OldName), ExecutableName = newApp.ExecutableName, FullPath = newApp.FullPath };
|
||||
oldApp = new Win32Program() { Name = Path.GetFileNameWithoutExtension(e.OldName), ExecutableName = newApp.ExecutableName, FullPath = newApp.FullPath };
|
||||
}
|
||||
else if (extension.Equals(urlExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
oldApp = new Win32() { Name = Path.GetFileNameWithoutExtension(e.OldName), ExecutableName = Path.GetFileName(e.OldName), FullPath = newApp.FullPath };
|
||||
oldApp = new Win32Program() { Name = Path.GetFileNameWithoutExtension(e.OldName), ExecutableName = Path.GetFileName(e.OldName), FullPath = newApp.FullPath };
|
||||
}
|
||||
else
|
||||
{
|
||||
oldApp = Win32.GetAppFromPath(oldPath);
|
||||
oldApp = Win32Program.GetAppFromPath(oldPath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -102,13 +104,14 @@ 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")]
|
||||
private void OnAppDeleted(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
string path = e.FullPath;
|
||||
string extension = Path.GetExtension(path);
|
||||
Programs.Win32 app = null;
|
||||
Programs.Win32Program app = null;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -123,7 +126,7 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
}
|
||||
else
|
||||
{
|
||||
app = Programs.Win32.GetAppFromPath(path);
|
||||
app = Programs.Win32Program.GetAppFromPath(path);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -138,11 +141,11 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
}
|
||||
|
||||
// When a URL application is deleted, we can no longer get the HashCode directly from the path because the FullPath a Url app is the URL obtained from reading the file
|
||||
private Win32 GetAppWithSameNameAndExecutable(string name, string executableName)
|
||||
private Win32Program GetAppWithSameNameAndExecutable(string name, string executableName)
|
||||
{
|
||||
foreach (Win32 app in Items)
|
||||
foreach (Win32Program app in Items)
|
||||
{
|
||||
if (name.Equals(app.Name, StringComparison.OrdinalIgnoreCase) && executableName.Equals(app.ExecutableName, StringComparison.OrdinalIgnoreCase))
|
||||
if (name.Equals(app.Name, StringComparison.CurrentCultureIgnoreCase) && executableName.Equals(app.ExecutableName, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
return app;
|
||||
}
|
||||
@@ -152,11 +155,11 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
|
||||
// To mitigate the issue faced (as stated above) when a shortcut application is renamed, the Exe FullPath and executable name must be obtained.
|
||||
// Unlike the rename event args, since we do not have a newPath, we iterate through all the programs and find the one with the same LnkResolved path.
|
||||
private Programs.Win32 GetAppWithSameLnkResolvedPath(string lnkResolvedPath)
|
||||
private Programs.Win32Program GetAppWithSameLnkResolvedPath(string lnkResolvedPath)
|
||||
{
|
||||
foreach (Programs.Win32 app in Items)
|
||||
foreach (Programs.Win32Program app in Items)
|
||||
{
|
||||
if (lnkResolvedPath.ToLower().Equals(app.LnkResolvedPath))
|
||||
if (lnkResolvedPath.ToLower(CultureInfo.CurrentCulture).Equals(app.LnkResolvedPath, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
return app;
|
||||
}
|
||||
@@ -167,9 +170,9 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
private void OnAppCreated(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
string path = e.FullPath;
|
||||
if (!Path.GetExtension(path).Equals(urlExtension))
|
||||
if (!Path.GetExtension(path).Equals(urlExtension, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
Programs.Win32 app = Programs.Win32.GetAppFromPath(path);
|
||||
Programs.Win32Program app = Programs.Win32Program.GetAppFromPath(path);
|
||||
if (app != null)
|
||||
{
|
||||
Add(app);
|
||||
@@ -180,9 +183,9 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
private void OnAppChanged(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
string path = e.FullPath;
|
||||
if (Path.GetExtension(path).Equals(urlExtension))
|
||||
if (Path.GetExtension(path).Equals(urlExtension, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
Programs.Win32 app = Programs.Win32.GetAppFromPath(path);
|
||||
Programs.Win32Program app = Programs.Win32Program.GetAppFromPath(path);
|
||||
if (app != null)
|
||||
{
|
||||
Add(app);
|
||||
@@ -192,7 +195,7 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
|
||||
public void IndexPrograms()
|
||||
{
|
||||
var applications = Programs.Win32.All(_settings);
|
||||
var applications = Programs.Win32Program.All(_settings);
|
||||
Set(applications);
|
||||
}
|
||||
|
||||
@@ -203,7 +206,7 @@ namespace Microsoft.Plugin.Program.Storage
|
||||
|
||||
public void Load()
|
||||
{
|
||||
var items = _storage.TryLoad(new Programs.Win32[] { });
|
||||
var items = _storage.TryLoad(Array.Empty<Win32Program>());
|
||||
Set(items);
|
||||
}
|
||||
|
||||
|
@@ -1,15 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Plugin.Program.Views.Models;
|
||||
|
||||
|
||||
namespace Microsoft.Plugin.Program.Views.Commands
|
||||
{
|
||||
internal static class ProgramSettingDisplay
|
||||
{
|
||||
internal static List<ProgramSource> LoadProgramSources(this List<Settings.ProgramSource> programSources)
|
||||
internal static List<ProgramSource> LoadProgramSources(this List<ProgramSource> programSources)
|
||||
{
|
||||
var list = new List<ProgramSource>();
|
||||
|
||||
@@ -89,7 +87,7 @@ namespace Microsoft.Plugin.Program.Views.Commands
|
||||
.ToList()
|
||||
.ForEach(x => Main._settings.DisabledProgramSources
|
||||
.Add(
|
||||
new Settings.DisabledProgramSource
|
||||
new DisabledProgramSource
|
||||
{
|
||||
Name = x.Name,
|
||||
Location = x.Location,
|
||||
@@ -114,13 +112,13 @@ namespace Microsoft.Plugin.Program.Views.Commands
|
||||
|
||||
internal static bool IsReindexRequired(this List<ProgramSource> selectedItems)
|
||||
{
|
||||
if (selectedItems.Where(t1 => t1.Enabled).Count() > 0)
|
||||
if (selectedItems.Where(t1 => t1.Enabled).Any())
|
||||
return true;
|
||||
|
||||
// ProgramSources holds list of user added directories,
|
||||
// so when we enable/disable we need to reindex to show/not show the programs
|
||||
// that are found in those directories.
|
||||
if (selectedItems.Where(t1 => Main._settings.ProgramSources.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Count() > 0)
|
||||
if (selectedItems.Where(t1 => Main._settings.ProgramSources.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier)).Any())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
@@ -1,5 +0,0 @@
|
||||
|
||||
namespace Microsoft.Plugin.Program.Views.Models
|
||||
{
|
||||
public class ProgramSource : Settings.ProgramSource { }
|
||||
}
|
@@ -5,13 +5,13 @@ using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.Plugin.Program.Views.Models;
|
||||
using Microsoft.Plugin.Program.Views.Commands;
|
||||
using Microsoft.Plugin.Program.Programs;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Data;
|
||||
using Wox.Plugin;
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Views
|
||||
{
|
||||
/// <summary>
|
||||
@@ -20,7 +20,7 @@ namespace Microsoft.Plugin.Program.Views
|
||||
public partial class ProgramSetting : UserControl
|
||||
{
|
||||
private PluginInitContext context;
|
||||
private Settings _settings;
|
||||
private ProgramPluginSettings _settings;
|
||||
private GridViewColumnHeader _lastHeaderClicked;
|
||||
private ListSortDirection _lastDirection;
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Microsoft.Plugin.Program.Views
|
||||
// this as temporary holder for displaying all loaded programs sources.
|
||||
internal static List<ProgramSource> ProgramSettingDisplayList { get; set; }
|
||||
|
||||
public ProgramSetting(PluginInitContext context, Settings settings, Programs.Win32[] win32s, UWP.Application[] uwps)
|
||||
public ProgramSetting(PluginInitContext context, ProgramPluginSettings settings)
|
||||
{
|
||||
this.context = context;
|
||||
InitializeComponent();
|
||||
@@ -79,7 +79,7 @@ namespace Microsoft.Plugin.Program.Views
|
||||
|
||||
private void btnEditProgramSource_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var selectedProgramSource = programSourceView.SelectedItem as Settings.ProgramSource;
|
||||
var selectedProgramSource = programSourceView.SelectedItem as ProgramSource;
|
||||
if (selectedProgramSource != null)
|
||||
{
|
||||
var add = new AddProgramSource(selectedProgramSource, _settings);
|
||||
@@ -143,7 +143,7 @@ namespace Microsoft.Plugin.Program.Views
|
||||
}
|
||||
}
|
||||
|
||||
if (directoriesToAdd.Count() > 0)
|
||||
if (directoriesToAdd.Count > 0)
|
||||
{
|
||||
directoriesToAdd.ForEach(x => _settings.ProgramSources.Add(x));
|
||||
directoriesToAdd.ForEach(x => ProgramSettingDisplayList.Add(x));
|
||||
@@ -179,7 +179,7 @@ namespace Microsoft.Plugin.Program.Views
|
||||
.SelectedItems.Cast<ProgramSource>()
|
||||
.ToList();
|
||||
|
||||
if (selectedItems.Count() == 0)
|
||||
if (selectedItems.Count == 0)
|
||||
{
|
||||
string msg = context.API.GetTranslation("wox_plugin_program_pls_select_program_source");
|
||||
MessageBox.Show(msg);
|
||||
@@ -190,9 +190,9 @@ namespace Microsoft.Plugin.Program.Views
|
||||
.Where(t1 => !_settings
|
||||
.ProgramSources
|
||||
.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
|
||||
.Count() == 0)
|
||||
.Any())
|
||||
{
|
||||
var msg = string.Format(context.API.GetTranslation("wox_plugin_program_delete_program_source"));
|
||||
var msg = string.Format(CultureInfo.CurrentCulture, context.API.GetTranslation("wox_plugin_program_delete_program_source"));
|
||||
|
||||
if (MessageBox.Show(msg, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
|
||||
{
|
||||
@@ -273,7 +273,7 @@ namespace Microsoft.Plugin.Program.Views
|
||||
dataView.Refresh();
|
||||
}
|
||||
|
||||
private bool IsSelectedRowStatusEnabledMoreOrEqualThanDisabled(List<ProgramSource> selectedItems)
|
||||
private static bool IsSelectedRowStatusEnabledMoreOrEqualThanDisabled(List<ProgramSource> selectedItems)
|
||||
{
|
||||
return selectedItems.Where(x => x.Enabled).Count() >= selectedItems.Where(x => !x.Enabled).Count();
|
||||
}
|
||||
@@ -288,7 +288,7 @@ namespace Microsoft.Plugin.Program.Views
|
||||
.Where(t1 => !_settings
|
||||
.ProgramSources
|
||||
.Any(x => t1.UniqueIdentifier == x.UniqueIdentifier))
|
||||
.Count() == 0)
|
||||
.Any())
|
||||
{
|
||||
btnProgramSourceStatus.Content = context.API.GetTranslation("wox_plugin_program_delete");
|
||||
return;
|
||||
|
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
using static Microsoft.Plugin.Program.Programs.UWP;
|
||||
|
||||
namespace Microsoft.Plugin.Program.Win32
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user