mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-04-05 02:36:19 +02:00
Add the Command Palette module (#37908)
Windows Command Palette ("CmdPal") is the next iteration of PowerToys Run. With extensibility at its core, the Command Palette is your one-stop launcher to start _anything_.
By default, CmdPal is bound to <kbd>Win+Alt+Space</kbd>.


----
This brings the current preview version of CmdPal into the upstream PowerToys repo. There are still lots of bugs to work out, but it's reached the state we're ready to start sharing it with the world. From here, we can further collaborate with the community on the features that are important, and ensuring that we've got a most robust API to enable developers to build whatever extensions they want.
Most of the built-in PT Run modules have already been ported to CmdPal's extension API. Those include:
* Installed apps
* Shell commands
* File search (powered by the indexer)
* Windows Registry search
* Web search
* Windows Terminal Profiles
* Windows Services
* Windows settings
There are a couple new extensions built-in
* You can now search for packages on `winget` and install them right from the palette. This also powers searching for extensions for the palette
* The calculator has an entirely new implementation. This is currently less feature complete than the original PT Run one - we're looking forward to updating it to be more complete for future ingestion in Windows
* "Bookmarks" allow you to save shortcuts to files, folders, and webpages as top-level commands in the palette.
We've got a bunch of other samples too, in this repo and elsewhere
### PowerToys specific notes
CmdPal will eventually graduate out of PowerToys to live as its own application, which is why it's implemented just a little differently than most other modules. Enabling CmdPal will install its `msix` package.
The CI was minorly changed to support CmdPal version numbers independent of PowerToys itself. It doesn't make sense for us to start CmdPal at v0.90, and in the future, we want to be able to rev CmdPal independently of PT itself.
Closes #3200, closes #3600, closes #7770, closes #34273, closes #36471, closes #20976, closes #14495
-----
TODOs et al
**Blocking:**
- [ ] Images and descriptions in Settings and OOBE need to be properly defined, as mentioned before
- [ ] Niels is on it
- [x] Doesn't start properly from PowerToys unless the fix PR is merged.
- https://github.com/zadjii-msft/PowerToys/pull/556 merged
- [x] I seem to lose focus a lot when I press on some limits, like between the search bar and the results.
- This is https://github.com/zadjii-msft/PowerToys/issues/427
- [x] Turned off an extension like Calculator and it was still working.
- Need to get rid of that toggle, it doesn't do anything currently
- [x] `ListViewModel.<FetchItems>` crash
- Pretty confident that was fixed in https://github.com/zadjii-msft/PowerToys/pull/553
**Not blocking / improvements:**
- Show the shortcut through settings, as mentioned before, or create a button that would open CmdPalette settings.
- When PowerToys starts, CmdPalette is always shown if enabled. That's weird when just starting PowerToys/ logging in to the computer with PowerToys auto-start activated. I think this should at least be a setting.
- Needing to double press a result for it to do the default action seems quirky. If one is already selected, I think just pressing should be enough for it to do the action.
- This is currently a setting, though we're thinking of changing the setting even more: https://github.com/zadjii-msft/PowerToys/issues/392
- There's no URI extension. Was surprised when typing a URL that it only proposed a web search.
- [x] There's no System commands extension. Was expecting to be able to quickly restart the computer by typing restart but it wasn't there.
- This is in PR https://github.com/zadjii-msft/PowerToys/pull/452
---------
Co-authored-by: joadoumie <98557455+joadoumie@users.noreply.github.com>
Co-authored-by: Jordi Adoumie <jordiadoumie@microsoft.com>
Co-authored-by: Mike Griese <zadjii@gmail.com>
Co-authored-by: Niels Laute <niels.laute@live.nl>
Co-authored-by: Michael Hawker <24302614+michael-hawker@users.noreply.github.com>
Co-authored-by: Stefan Markovic <57057282+stefansjfw@users.noreply.github.com>
Co-authored-by: Seraphima <zykovas91@gmail.com>
Co-authored-by: Jaime Bernardo <jaime@janeasystems.com>
Co-authored-by: Kristen Schau <47155823+krschau@users.noreply.github.com>
Co-authored-by: Eric Johnson <ericjohnson327@gmail.com>
Co-authored-by: Ethan Fang <ethanfang@microsoft.com>
Co-authored-by: Yu Leng (from Dev Box) <yuleng@microsoft.com>
Co-authored-by: Clint Rutkas <clint@rutkas.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps;
|
||||
|
||||
public class AllAppsSettings : JsonSettingsManager
|
||||
{
|
||||
private static readonly string _namespace = "apps";
|
||||
|
||||
private static string Namespaced(string propertyName) => $"{_namespace}.{propertyName}";
|
||||
|
||||
private static string Experimental(string propertyName) => $"{_namespace}.experimental.{propertyName}";
|
||||
|
||||
#pragma warning disable SA1401 // Fields should be private
|
||||
internal static AllAppsSettings Instance = new();
|
||||
#pragma warning restore SA1401 // Fields should be private
|
||||
|
||||
public DateTime LastIndexTime { get; set; }
|
||||
|
||||
public List<ProgramSource> ProgramSources { get; set; } = [];
|
||||
|
||||
public List<DisabledProgramSource> DisabledProgramSources { get; set; } = [];
|
||||
|
||||
public List<string> ProgramSuffixes { get; set; } = ["bat", "appref-ms", "exe", "lnk", "url"];
|
||||
|
||||
public List<string> RunCommandSuffixes { get; set; } = ["bat", "appref-ms", "exe", "lnk", "url", "cpl", "msc"];
|
||||
|
||||
public bool EnableStartMenuSource => _enableStartMenuSource.Value;
|
||||
|
||||
public bool EnableDesktopSource => _enableDesktopSource.Value;
|
||||
|
||||
public bool EnableRegistrySource => _enableRegistrySource.Value;
|
||||
|
||||
public bool EnablePathEnvironmentVariableSource => _enablePathEnvironmentVariableSource.Value;
|
||||
|
||||
private readonly ToggleSetting _enableStartMenuSource = new(
|
||||
Namespaced(nameof(EnableStartMenuSource)),
|
||||
Resources.enable_start_menu_source,
|
||||
Resources.enable_start_menu_source,
|
||||
true);
|
||||
|
||||
private readonly ToggleSetting _enableDesktopSource = new(
|
||||
Namespaced(nameof(EnableDesktopSource)),
|
||||
Resources.enable_desktop_source,
|
||||
Resources.enable_desktop_source,
|
||||
true);
|
||||
|
||||
private readonly ToggleSetting _enableRegistrySource = new(
|
||||
Namespaced(nameof(EnableRegistrySource)),
|
||||
Resources.enable_registry_source,
|
||||
Resources.enable_registry_source,
|
||||
false); // This one is very noisy
|
||||
|
||||
private readonly ToggleSetting _enablePathEnvironmentVariableSource = new(
|
||||
Namespaced(nameof(EnablePathEnvironmentVariableSource)),
|
||||
Resources.enable_path_environment_variable_source,
|
||||
Resources.enable_path_environment_variable_source,
|
||||
false); // this one is very VERY noisy
|
||||
|
||||
public double MinScoreThreshold { get; set; } = 0.75;
|
||||
|
||||
internal const char SuffixSeparator = ';';
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
string directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
// now, the state is just next to the exe
|
||||
return Path.Combine(directory, "settings.json");
|
||||
}
|
||||
|
||||
public AllAppsSettings()
|
||||
{
|
||||
FilePath = SettingsJsonPath();
|
||||
|
||||
Settings.Add(_enableStartMenuSource);
|
||||
Settings.Add(_enableDesktopSource);
|
||||
Settings.Add(_enableRegistrySource);
|
||||
Settings.Add(_enablePathEnvironmentVariableSource);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
|
||||
Settings.SettingsChanged += (s, a) => this.SaveSettings();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using WyHash;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps;
|
||||
|
||||
internal sealed partial class AppCommand : InvokableCommand
|
||||
{
|
||||
private readonly AppItem _app;
|
||||
|
||||
internal AppCommand(AppItem app)
|
||||
{
|
||||
_app = app;
|
||||
|
||||
Name = Resources.run_command_action;
|
||||
Id = GenerateId();
|
||||
}
|
||||
|
||||
internal static async Task StartApp(string aumid)
|
||||
{
|
||||
var appManager = new ApplicationActivationManager();
|
||||
const ActivateOptions noFlags = ActivateOptions.None;
|
||||
await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
appManager.ActivateApplication(aumid, /*queryArguments*/ string.Empty, noFlags, out var unusedPid);
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task StartExe(string path)
|
||||
{
|
||||
var appManager = new ApplicationActivationManager();
|
||||
|
||||
// const ActivateOptions noFlags = ActivateOptions.None;
|
||||
await Task.Run(() =>
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
||||
});
|
||||
}
|
||||
|
||||
internal async Task Launch()
|
||||
{
|
||||
if (_app.IsPackaged)
|
||||
{
|
||||
await StartApp(_app.UserModelId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await StartExe(_app.ExePath);
|
||||
}
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
_ = Launch();
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
|
||||
private string GenerateId()
|
||||
{
|
||||
// Use WyHash64 to generate stable ID hashes.
|
||||
// manually seeding with 0, so that the hash is stable across launches
|
||||
var result = WyHash64.ComputeHash64(_app.Name + _app.Subtitle + _app.ExePath, seed: 0);
|
||||
return $"{_app.Name}_{result}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
|
||||
internal sealed partial class OpenInConsoleCommand : InvokableCommand
|
||||
{
|
||||
private static readonly IconInfo TheIcon = new("\ue838");
|
||||
|
||||
private readonly string _target;
|
||||
|
||||
public OpenInConsoleCommand(string target)
|
||||
{
|
||||
Name = Resources.open_path_in_console;
|
||||
Icon = TheIcon;
|
||||
|
||||
_target = target;
|
||||
}
|
||||
|
||||
internal static async Task LaunchTarget(string t)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var processStartInfo = new ProcessStartInfo
|
||||
{
|
||||
WorkingDirectory = t,
|
||||
FileName = "cmd.exe",
|
||||
};
|
||||
|
||||
Process.Start(processStartInfo);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Log.Exception($"Failed to open {Name} in console, {e.Message}", e, GetType());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
_ = LaunchTarget(_target).ConfigureAwait(false);
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
|
||||
internal sealed partial class OpenPathCommand : InvokableCommand
|
||||
{
|
||||
private static readonly IconInfo TheIcon = new("\ue838");
|
||||
|
||||
private readonly string _target;
|
||||
|
||||
public OpenPathCommand(string target)
|
||||
{
|
||||
Name = Resources.open_location;
|
||||
Icon = TheIcon;
|
||||
|
||||
_target = target;
|
||||
}
|
||||
|
||||
internal static async Task LaunchTarget(string t)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(t) { UseShellExecute = true });
|
||||
});
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
_ = LaunchTarget(_target).ConfigureAwait(false);
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
|
||||
internal sealed partial class RunAsAdminCommand : InvokableCommand
|
||||
{
|
||||
private static readonly IconInfo TheIcon = new("\uE7EF");
|
||||
|
||||
private readonly string _target;
|
||||
private readonly string _parentDir;
|
||||
private readonly bool _packaged;
|
||||
|
||||
public RunAsAdminCommand(string target, string parentDir, bool packaged)
|
||||
{
|
||||
Name = Resources.run_as_administrator;
|
||||
Icon = TheIcon;
|
||||
|
||||
_target = target;
|
||||
_parentDir = parentDir;
|
||||
_packaged = packaged;
|
||||
}
|
||||
|
||||
internal static async Task RunAsAdmin(string target, string parentDir, bool packaged)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
if (packaged)
|
||||
{
|
||||
var command = "shell:AppsFolder\\" + target;
|
||||
command = Environment.ExpandEnvironmentVariables(command.Trim());
|
||||
|
||||
var info = ShellCommand.SetProcessStartInfo(command, verb: "runas");
|
||||
info.UseShellExecute = true;
|
||||
info.Arguments = string.Empty;
|
||||
Process.Start(info);
|
||||
}
|
||||
else
|
||||
{
|
||||
var info = ShellCommand.GetProcessStartInfo(target, parentDir, string.Empty, ShellCommand.RunAsType.Administrator);
|
||||
|
||||
Process.Start(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
_ = RunAsAdmin(_target, _parentDir, _packaged).ConfigureAwait(false);
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Apps.Properties;
|
||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Commands;
|
||||
|
||||
internal sealed partial class RunAsUserCommand : InvokableCommand
|
||||
{
|
||||
private static readonly IconInfo TheIcon = new("\uE7EE");
|
||||
|
||||
private readonly string _target;
|
||||
private readonly string _parentDir;
|
||||
|
||||
public RunAsUserCommand(string target, string parentDir)
|
||||
{
|
||||
Name = Resources.run_as_different_user;
|
||||
Icon = TheIcon;
|
||||
|
||||
_target = target;
|
||||
_parentDir = parentDir;
|
||||
}
|
||||
|
||||
internal static async Task RunAsAdmin(string target, string parentDir)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
var info = ShellCommand.GetProcessStartInfo(target, parentDir, string.Empty, ShellCommand.RunAsType.OtherUser);
|
||||
|
||||
Process.Start(info);
|
||||
});
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
_ = RunAsAdmin(_target, _parentDir).ConfigureAwait(false);
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
public class DisabledProgramSource : ProgramSource
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.IO.Abstractions;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
public interface IFileVersionInfoWrapper
|
||||
{
|
||||
FileVersionInfo? GetVersionInfo(string path);
|
||||
|
||||
string FileDescription { get; set; }
|
||||
}
|
||||
|
||||
public class FileVersionInfoWrapper : IFileVersionInfoWrapper
|
||||
{
|
||||
private readonly IFile _file;
|
||||
|
||||
public FileVersionInfoWrapper()
|
||||
: this(new FileSystem().File)
|
||||
{
|
||||
}
|
||||
|
||||
public FileVersionInfoWrapper(IFile file)
|
||||
{
|
||||
_file = file;
|
||||
}
|
||||
|
||||
public FileVersionInfo? GetVersionInfo(string path)
|
||||
{
|
||||
if (_file.Exists(path))
|
||||
{
|
||||
return FileVersionInfo.GetVersionInfo(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string FileDescription { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
internal interface IPackageCatalog
|
||||
{
|
||||
event TypedEventHandler<PackageCatalog, PackageInstallingEventArgs> PackageInstalling;
|
||||
|
||||
event TypedEventHandler<PackageCatalog, PackageUninstallingEventArgs> PackageUninstalling;
|
||||
|
||||
event TypedEventHandler<PackageCatalog, PackageUpdatingEventArgs> PackageUpdating;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
internal sealed class PackageCatalogWrapper : IPackageCatalog
|
||||
{
|
||||
private PackageCatalog _packageCatalog;
|
||||
|
||||
public PackageCatalogWrapper()
|
||||
{
|
||||
// Opens the catalog of packages that is available for the current user.
|
||||
_packageCatalog = PackageCatalog.OpenForCurrentUser();
|
||||
}
|
||||
|
||||
// Summary: Indicates that an app package is installing.
|
||||
public event TypedEventHandler<PackageCatalog, PackageInstallingEventArgs> PackageInstalling
|
||||
{
|
||||
add
|
||||
{
|
||||
_packageCatalog.PackageInstalling += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
_packageCatalog.PackageInstalling -= value;
|
||||
}
|
||||
}
|
||||
|
||||
// Summary: Indicates that an app package is uninstalling.
|
||||
public event TypedEventHandler<PackageCatalog, PackageUninstallingEventArgs> PackageUninstalling
|
||||
{
|
||||
add
|
||||
{
|
||||
_packageCatalog.PackageUninstalling += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
_packageCatalog.PackageUninstalling -= value;
|
||||
}
|
||||
}
|
||||
|
||||
// Summary: Indicates that an app package is updating.
|
||||
public event TypedEventHandler<PackageCatalog, PackageUpdatingEventArgs> PackageUpdating
|
||||
{
|
||||
add
|
||||
{
|
||||
_packageCatalog.PackageUpdating += value;
|
||||
}
|
||||
|
||||
remove
|
||||
{
|
||||
_packageCatalog.PackageUpdating -= value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to NTFS reparse points in .Net.
|
||||
/// </summary>
|
||||
public static class ReparsePoint
|
||||
{
|
||||
#pragma warning disable SA1310 // Field names should not contain underscore
|
||||
|
||||
private const int ERROR_NOT_A_REPARSE_POINT = 4390;
|
||||
|
||||
private const int ERROR_INSUFFICIENT_BUFFER = 122;
|
||||
|
||||
private const int ERROR_MORE_DATA = 234;
|
||||
|
||||
private const int FSCTL_GET_REPARSE_POINT = 0x000900A8;
|
||||
|
||||
private const uint IO_REPARSE_TAG_APPEXECLINK = 0x8000001B;
|
||||
|
||||
private const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024;
|
||||
|
||||
private const int E_INVALID_PROTOCOL_FORMAT = unchecked((int)0x83760002);
|
||||
#pragma warning restore SA1310 // Field names should not contain underscore
|
||||
|
||||
[Flags]
|
||||
private enum FileAccessType : uint
|
||||
{
|
||||
DELETE = 0x00010000,
|
||||
READ_CONTROL = 0x00020000,
|
||||
WRITE_DAC = 0x00040000,
|
||||
WRITE_OWNER = 0x00080000,
|
||||
SYNCHRONIZE = 0x00100000,
|
||||
|
||||
STANDARD_RIGHTS_REQUIRED = 0x000F0000,
|
||||
|
||||
STANDARD_RIGHTS_READ = READ_CONTROL,
|
||||
STANDARD_RIGHTS_WRITE = READ_CONTROL,
|
||||
STANDARD_RIGHTS_EXECUTE = READ_CONTROL,
|
||||
|
||||
STANDARD_RIGHTS_ALL = 0x001F0000,
|
||||
|
||||
SPECIFIC_RIGHTS_ALL = 0x0000FFFF,
|
||||
|
||||
ACCESS_SYSTEM_SECURITY = 0x01000000,
|
||||
|
||||
MAXIMUM_ALLOWED = 0x02000000,
|
||||
|
||||
GENERIC_READ = 0x80000000,
|
||||
GENERIC_WRITE = 0x40000000,
|
||||
GENERIC_EXECUTE = 0x20000000,
|
||||
GENERIC_ALL = 0x10000000,
|
||||
|
||||
FILE_READ_DATA = 0x0001,
|
||||
FILE_WRITE_DATA = 0x0002,
|
||||
FILE_APPEND_DATA = 0x0004,
|
||||
FILE_READ_EA = 0x0008,
|
||||
FILE_WRITE_EA = 0x0010,
|
||||
FILE_EXECUTE = 0x0020,
|
||||
FILE_READ_ATTRIBUTES = 0x0080,
|
||||
FILE_WRITE_ATTRIBUTES = 0x0100,
|
||||
|
||||
FILE_ALL_ACCESS =
|
||||
STANDARD_RIGHTS_REQUIRED |
|
||||
SYNCHRONIZE
|
||||
| 0x1FF,
|
||||
|
||||
FILE_GENERIC_READ =
|
||||
STANDARD_RIGHTS_READ |
|
||||
FILE_READ_DATA |
|
||||
FILE_READ_ATTRIBUTES |
|
||||
FILE_READ_EA |
|
||||
SYNCHRONIZE,
|
||||
|
||||
FILE_GENERIC_WRITE =
|
||||
STANDARD_RIGHTS_WRITE |
|
||||
FILE_WRITE_DATA |
|
||||
FILE_WRITE_ATTRIBUTES |
|
||||
FILE_WRITE_EA |
|
||||
FILE_APPEND_DATA |
|
||||
SYNCHRONIZE,
|
||||
|
||||
FILE_GENERIC_EXECUTE =
|
||||
STANDARD_RIGHTS_EXECUTE |
|
||||
FILE_READ_ATTRIBUTES |
|
||||
FILE_EXECUTE |
|
||||
SYNCHRONIZE,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum FileShareType : uint
|
||||
{
|
||||
None = 0x00000000,
|
||||
Read = 0x00000001,
|
||||
Write = 0x00000002,
|
||||
Delete = 0x00000004,
|
||||
}
|
||||
|
||||
private enum CreationDisposition : uint
|
||||
{
|
||||
New = 1,
|
||||
CreateAlways = 2,
|
||||
OpenExisting = 3,
|
||||
OpenAlways = 4,
|
||||
TruncateExisting = 5,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum FileAttributes : uint
|
||||
{
|
||||
Readonly = 0x00000001,
|
||||
Hidden = 0x00000002,
|
||||
System = 0x00000004,
|
||||
Directory = 0x00000010,
|
||||
Archive = 0x00000020,
|
||||
Device = 0x00000040,
|
||||
Normal = 0x00000080,
|
||||
Temporary = 0x00000100,
|
||||
SparseFile = 0x00000200,
|
||||
ReparsePoint = 0x00000400,
|
||||
Compressed = 0x00000800,
|
||||
Offline = 0x00001000,
|
||||
NotContentIndexed = 0x00002000,
|
||||
Encrypted = 0x00004000,
|
||||
Write_Through = 0x80000000,
|
||||
Overlapped = 0x40000000,
|
||||
NoBuffering = 0x20000000,
|
||||
RandomAccess = 0x10000000,
|
||||
SequentialScan = 0x08000000,
|
||||
DeleteOnClose = 0x04000000,
|
||||
BackupSemantics = 0x02000000,
|
||||
PosixSemantics = 0x01000000,
|
||||
OpenReparsePoint = 0x00200000,
|
||||
OpenNoRecall = 0x00100000,
|
||||
FirstPipeInstance = 0x00080000,
|
||||
}
|
||||
|
||||
private enum AppExecutionAliasReparseTagBufferLayoutVersion : uint
|
||||
{
|
||||
Invalid = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Initial version used package full name, aumid, exe path
|
||||
/// </summary>
|
||||
Initial = 1,
|
||||
|
||||
/// <summary>
|
||||
/// This version replaces package full name with family name, to allow
|
||||
/// optional packages to reference their main package across versions.
|
||||
/// </summary>
|
||||
PackageFamilyName = 2,
|
||||
|
||||
/// <summary>
|
||||
/// This version appends a flag to the family Name version to differentiate
|
||||
/// between UWP and Centennial
|
||||
/// </summary>
|
||||
MultiAppTypeSupport = 3,
|
||||
|
||||
/// <summary>
|
||||
/// Used to check version validity, where valid is (Invalid, UpperBound)
|
||||
/// </summary>
|
||||
UpperBound,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct AppExecutionAliasReparseTagHeader
|
||||
{
|
||||
/// <summary>
|
||||
/// Reparse point tag.
|
||||
/// </summary>
|
||||
public uint ReparseTag;
|
||||
|
||||
/// <summary>
|
||||
/// Size, in bytes, of the data after the Reserved member.
|
||||
/// </summary>
|
||||
public ushort ReparseDataLength;
|
||||
|
||||
/// <summary>
|
||||
/// Reserved; do not use.
|
||||
/// </summary>
|
||||
public ushort Reserved;
|
||||
|
||||
public AppExecutionAliasReparseTagBufferLayoutVersion Version;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
||||
private static extern bool DeviceIoControl(
|
||||
IntPtr hDevice,
|
||||
uint dwIoControlCode,
|
||||
IntPtr inBuffer,
|
||||
int nInBufferSize,
|
||||
IntPtr outBuffer,
|
||||
int nOutBufferSize,
|
||||
out int pBytesReturned,
|
||||
IntPtr lpOverlapped);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern IntPtr CreateFile(
|
||||
string lpFileName,
|
||||
FileAccessType dwDesiredAccess,
|
||||
FileShareType dwShareMode,
|
||||
IntPtr lpSecurityAttributes,
|
||||
CreationDisposition dwCreationDisposition,
|
||||
FileAttributes dwFlagsAndAttributes,
|
||||
IntPtr hTemplateFile);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the target of the specified reparse point.
|
||||
/// </summary>
|
||||
/// <param name="reparsePoint">The path of the reparse point.</param>
|
||||
/// <returns>
|
||||
/// The target of the reparse point.
|
||||
/// </returns>
|
||||
/// <exception cref="IOException">
|
||||
/// Thrown when the reparse point specified is not a reparse point or is invalid.
|
||||
/// </exception>
|
||||
public static string? GetTarget(string reparsePoint)
|
||||
{
|
||||
using (SafeFileHandle reparsePointHandle = new SafeFileHandle(
|
||||
CreateFile(
|
||||
reparsePoint,
|
||||
FileAccessType.FILE_READ_ATTRIBUTES | FileAccessType.FILE_READ_EA,
|
||||
FileShareType.Delete | FileShareType.Read | FileShareType.Write,
|
||||
IntPtr.Zero,
|
||||
CreationDisposition.OpenExisting,
|
||||
FileAttributes.OpenReparsePoint,
|
||||
IntPtr.Zero),
|
||||
true))
|
||||
{
|
||||
if (Marshal.GetLastWin32Error() != 0)
|
||||
{
|
||||
ThrowLastWin32Error("Unable to open reparse point.");
|
||||
}
|
||||
|
||||
var outBufferSize = 512;
|
||||
var outBuffer = Marshal.AllocHGlobal(outBufferSize);
|
||||
|
||||
try
|
||||
{
|
||||
// For-loop allows an attempt with 512-bytes buffer, before retrying with a 'MAXIMUM_REPARSE_DATA_BUFFER_SIZE' bytes buffer.
|
||||
for (var i = 0; i < 2; ++i)
|
||||
{
|
||||
int bytesReturned;
|
||||
var result = DeviceIoControl(
|
||||
reparsePointHandle.DangerousGetHandle(),
|
||||
FSCTL_GET_REPARSE_POINT,
|
||||
IntPtr.Zero,
|
||||
0,
|
||||
outBuffer,
|
||||
outBufferSize,
|
||||
out bytesReturned,
|
||||
IntPtr.Zero);
|
||||
|
||||
if (!result)
|
||||
{
|
||||
var error = Marshal.GetLastWin32Error();
|
||||
if (error == ERROR_NOT_A_REPARSE_POINT)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((error == ERROR_INSUFFICIENT_BUFFER) || (error == ERROR_MORE_DATA))
|
||||
{
|
||||
Marshal.FreeHGlobal(outBuffer);
|
||||
outBuffer = IntPtr.Zero;
|
||||
|
||||
outBufferSize = MAXIMUM_REPARSE_DATA_BUFFER_SIZE;
|
||||
outBuffer = Marshal.AllocHGlobal(outBufferSize);
|
||||
continue;
|
||||
}
|
||||
|
||||
ThrowLastWin32Error("Unable to get information about reparse point.");
|
||||
}
|
||||
|
||||
AppExecutionAliasReparseTagHeader aliasReparseHeader = Marshal.PtrToStructure<AppExecutionAliasReparseTagHeader>(outBuffer);
|
||||
|
||||
if (aliasReparseHeader.ReparseTag == IO_REPARSE_TAG_APPEXECLINK)
|
||||
{
|
||||
var metadata = AppExecutionAliasMetadata.FromPersistedRepresentationIntPtr(
|
||||
outBuffer,
|
||||
aliasReparseHeader.Version);
|
||||
|
||||
return metadata.ExePath;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(outBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private sealed class AppExecutionAliasMetadata
|
||||
{
|
||||
public string PackageFullName { get; init; } = string.Empty;
|
||||
|
||||
public string PackageFamilyName { get; init; } = string.Empty;
|
||||
|
||||
public string Aumid { get; init; } = string.Empty;
|
||||
|
||||
public string ExePath { get; init; } = string.Empty;
|
||||
|
||||
public static AppExecutionAliasMetadata FromPersistedRepresentationIntPtr(IntPtr reparseDataBufferPtr, AppExecutionAliasReparseTagBufferLayoutVersion version)
|
||||
{
|
||||
var dataOffset = Marshal.SizeOf(typeof(AppExecutionAliasReparseTagHeader));
|
||||
var dataBufferPtr = reparseDataBufferPtr + dataOffset;
|
||||
|
||||
string? packageFullName = null;
|
||||
string? packageFamilyName = null;
|
||||
string? aumid = null;
|
||||
string? exePath = null;
|
||||
|
||||
VerifyVersion(version);
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case AppExecutionAliasReparseTagBufferLayoutVersion.Initial:
|
||||
packageFullName = Marshal.PtrToStringUni(dataBufferPtr);
|
||||
if (packageFullName is not null)
|
||||
{
|
||||
dataBufferPtr += Encoding.Unicode.GetByteCount(packageFullName) + Encoding.Unicode.GetByteCount("\0");
|
||||
aumid = Marshal.PtrToStringUni(dataBufferPtr);
|
||||
|
||||
if (aumid is not null)
|
||||
{
|
||||
dataBufferPtr += Encoding.Unicode.GetByteCount(aumid) + Encoding.Unicode.GetByteCount("\0");
|
||||
exePath = Marshal.PtrToStringUni(dataBufferPtr);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case AppExecutionAliasReparseTagBufferLayoutVersion.PackageFamilyName:
|
||||
case AppExecutionAliasReparseTagBufferLayoutVersion.MultiAppTypeSupport:
|
||||
packageFamilyName = Marshal.PtrToStringUni(dataBufferPtr);
|
||||
|
||||
if (packageFamilyName is not null)
|
||||
{
|
||||
dataBufferPtr += Encoding.Unicode.GetByteCount(packageFamilyName) + Encoding.Unicode.GetByteCount("\0");
|
||||
aumid = Marshal.PtrToStringUni(dataBufferPtr);
|
||||
|
||||
if (aumid is not null)
|
||||
{
|
||||
dataBufferPtr += Encoding.Unicode.GetByteCount(aumid) + Encoding.Unicode.GetByteCount("\0");
|
||||
|
||||
exePath = Marshal.PtrToStringUni(dataBufferPtr);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return new AppExecutionAliasMetadata
|
||||
{
|
||||
PackageFullName = packageFullName ?? string.Empty,
|
||||
PackageFamilyName = packageFamilyName ?? string.Empty,
|
||||
Aumid = aumid ?? string.Empty,
|
||||
ExePath = exePath ?? string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
private static void VerifyVersion(AppExecutionAliasReparseTagBufferLayoutVersion version)
|
||||
{
|
||||
var uintVersion = (uint)version;
|
||||
|
||||
if (uintVersion > (uint)AppExecutionAliasReparseTagBufferLayoutVersion.Invalid &&
|
||||
uintVersion < (uint)AppExecutionAliasReparseTagBufferLayoutVersion.UpperBound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new IOException("Invalid app execution alias reparse version.", E_INVALID_PROTOCOL_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ThrowLastWin32Error(string message)
|
||||
{
|
||||
throw new IOException(message, Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
|
||||
}
|
||||
}
|
||||
270
src/modules/cmdpal/exts/Microsoft.CmdPal.Ext.Apps/Properties/Resources.Designer.cs
generated
Normal file
270
src/modules/cmdpal/exts/Microsoft.CmdPal.Ext.Apps/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,270 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Ext.Apps.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to All Apps.
|
||||
/// </summary>
|
||||
internal static string all_apps {
|
||||
get {
|
||||
return ResourceManager.GetString("all_apps", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Application.
|
||||
/// </summary>
|
||||
internal static string application {
|
||||
get {
|
||||
return ResourceManager.GetString("application", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Include apps found on the desktop.
|
||||
/// </summary>
|
||||
internal static string enable_desktop_source {
|
||||
get {
|
||||
return ResourceManager.GetString("enable_desktop_source", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Include apps anywhere on the %PATH%.
|
||||
/// </summary>
|
||||
internal static string enable_path_environment_variable_source {
|
||||
get {
|
||||
return ResourceManager.GetString("enable_path_environment_variable_source", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Include apps registered in the Registry.
|
||||
/// </summary>
|
||||
internal static string enable_registry_source {
|
||||
get {
|
||||
return ResourceManager.GetString("enable_registry_source", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Include apps found in the Start Menu.
|
||||
/// </summary>
|
||||
internal static string enable_start_menu_source {
|
||||
get {
|
||||
return ResourceManager.GetString("enable_start_menu_source", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to File.
|
||||
/// </summary>
|
||||
internal static string file {
|
||||
get {
|
||||
return ResourceManager.GetString("file", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Folder.
|
||||
/// </summary>
|
||||
internal static string folder {
|
||||
get {
|
||||
return ResourceManager.GetString("folder", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Installed apps.
|
||||
/// </summary>
|
||||
internal static string installed_apps {
|
||||
get {
|
||||
return ResourceManager.GetString("installed_apps", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Internet shortcut application.
|
||||
/// </summary>
|
||||
internal static string internet_shortcut_application {
|
||||
get {
|
||||
return ResourceManager.GetString("internet_shortcut_application", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open containing folder.
|
||||
/// </summary>
|
||||
internal static string open_containing_folder {
|
||||
get {
|
||||
return ResourceManager.GetString("open_containing_folder", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open location.
|
||||
/// </summary>
|
||||
internal static string open_location {
|
||||
get {
|
||||
return ResourceManager.GetString("open_location", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open path in console.
|
||||
/// </summary>
|
||||
internal static string open_path_in_console {
|
||||
get {
|
||||
return ResourceManager.GetString("open_path_in_console", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Packaged application.
|
||||
/// </summary>
|
||||
internal static string packaged_application {
|
||||
get {
|
||||
return ResourceManager.GetString("packaged_application", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Run as administrator.
|
||||
/// </summary>
|
||||
internal static string run_as_administrator {
|
||||
get {
|
||||
return ResourceManager.GetString("run_as_administrator", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Run as different user.
|
||||
/// </summary>
|
||||
internal static string run_as_different_user {
|
||||
get {
|
||||
return ResourceManager.GetString("run_as_different_user", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Run command.
|
||||
/// </summary>
|
||||
internal static string run_command {
|
||||
get {
|
||||
return ResourceManager.GetString("run_command", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Run.
|
||||
/// </summary>
|
||||
internal static string run_command_action {
|
||||
get {
|
||||
return ResourceManager.GetString("run_command_action", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search installed apps.
|
||||
/// </summary>
|
||||
internal static string search_installed_apps {
|
||||
get {
|
||||
return ResourceManager.GetString("search_installed_apps", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search installed apps....
|
||||
/// </summary>
|
||||
internal static string search_installed_apps_placeholder {
|
||||
get {
|
||||
return ResourceManager.GetString("search_installed_apps_placeholder", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Experimental: When enabled, Command Palette will load thumbnails from the Windows Shell. Using thumbnails may cause the app to crash on launch.
|
||||
/// </summary>
|
||||
internal static string use_thumbnails_setting_description {
|
||||
get {
|
||||
return ResourceManager.GetString("use_thumbnails_setting_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Use thumbnails for apps.
|
||||
/// </summary>
|
||||
internal static string use_thumbnails_setting_label {
|
||||
get {
|
||||
return ResourceManager.GetString("use_thumbnails_setting_label", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Web application.
|
||||
/// </summary>
|
||||
internal static string web_application {
|
||||
get {
|
||||
return ResourceManager.GetString("web_application", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="run_command_action" xml:space="preserve">
|
||||
<value>Run</value>
|
||||
<comment>As in "run command"</comment>
|
||||
</data>
|
||||
<data name="installed_apps" xml:space="preserve">
|
||||
<value>Installed apps</value>
|
||||
</data>
|
||||
<data name="search_installed_apps" xml:space="preserve">
|
||||
<value>Search installed apps</value>
|
||||
</data>
|
||||
<data name="all_apps" xml:space="preserve">
|
||||
<value>All Apps</value>
|
||||
</data>
|
||||
<data name="search_installed_apps_placeholder" xml:space="preserve">
|
||||
<value>Search installed apps...</value>
|
||||
</data>
|
||||
<data name="open_path_in_console" xml:space="preserve">
|
||||
<value>Open path in console</value>
|
||||
</data>
|
||||
<data name="packaged_application" xml:space="preserve">
|
||||
<value>Packaged application</value>
|
||||
</data>
|
||||
<data name="open_containing_folder" xml:space="preserve">
|
||||
<value>Open containing folder</value>
|
||||
</data>
|
||||
<data name="application" xml:space="preserve">
|
||||
<value>Application</value>
|
||||
</data>
|
||||
<data name="internet_shortcut_application" xml:space="preserve">
|
||||
<value>Internet shortcut application</value>
|
||||
</data>
|
||||
<data name="web_application" xml:space="preserve">
|
||||
<value>Web application</value>
|
||||
</data>
|
||||
<data name="run_command" xml:space="preserve">
|
||||
<value>Run command</value>
|
||||
</data>
|
||||
<data name="folder" xml:space="preserve">
|
||||
<value>Folder</value>
|
||||
</data>
|
||||
<data name="file" xml:space="preserve">
|
||||
<value>File</value>
|
||||
</data>
|
||||
<data name="open_location" xml:space="preserve">
|
||||
<value>Open location</value>
|
||||
</data>
|
||||
<data name="run_as_administrator" xml:space="preserve">
|
||||
<value>Run as administrator</value>
|
||||
</data>
|
||||
<data name="run_as_different_user" xml:space="preserve">
|
||||
<value>Run as different user</value>
|
||||
</data>
|
||||
<data name="enable_start_menu_source" xml:space="preserve">
|
||||
<value>Include apps found in the Start Menu</value>
|
||||
</data>
|
||||
<data name="enable_desktop_source" xml:space="preserve">
|
||||
<value>Include apps found on the desktop</value>
|
||||
</data>
|
||||
<data name="enable_registry_source" xml:space="preserve">
|
||||
<value>Include apps registered in the Registry</value>
|
||||
</data>
|
||||
<data name="enable_path_environment_variable_source" xml:space="preserve">
|
||||
<value>Include apps anywhere on the %PATH%</value>
|
||||
</data>
|
||||
<data name="use_thumbnails_setting_label" xml:space="preserve">
|
||||
<value>Use thumbnails for apps</value>
|
||||
<comment>when enabled, we'll display thumbnails from the windows shell for programs</comment>
|
||||
</data>
|
||||
<data name="use_thumbnails_setting_description" xml:space="preserve">
|
||||
<value>Experimental: When enabled, Command Palette will load thumbnails from the Windows Shell. Using thumbnails may cause the app to crash on launch</value>
|
||||
<comment>A description for "use_thumbnails_setting_label"</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
public static class EventHandler
|
||||
{
|
||||
// To obtain the path of the app when multiple events are added to the Concurrent queue across multiple threads.
|
||||
// On the first occurrence of a different file path, the existing app path is to be returned without removing any more elements from the queue.
|
||||
public static async Task<string> GetAppPathFromQueueAsync(ConcurrentQueue<string> eventHandlingQueue, int dequeueDelay)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(eventHandlingQueue);
|
||||
|
||||
var previousAppPath = string.Empty;
|
||||
|
||||
// To obtain the last event associated with a particular app.
|
||||
while (eventHandlingQueue.TryPeek(out var currentAppPath))
|
||||
{
|
||||
// Using OrdinalIgnoreCase since this is used internally with paths
|
||||
if (string.IsNullOrEmpty(previousAppPath) || previousAppPath.Equals(currentAppPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// To dequeue a path only if it is the first one in the queue or if the path was the same as the previous one (to avoid trying to create apps on duplicate events)
|
||||
previousAppPath = currentAppPath;
|
||||
eventHandlingQueue.TryDequeue(out _);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// This delay has been added to account for the delay in events being triggered during app installation.
|
||||
await Task.Delay(dequeueDelay).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return previousAppPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
// File System Watcher Wrapper class which implements the IFileSystemWatcherWrapper interface
|
||||
public sealed class FileSystemWatcherWrapper : FileSystemWatcher, IFileSystemWatcherWrapper
|
||||
{
|
||||
public FileSystemWatcherWrapper()
|
||||
{
|
||||
}
|
||||
|
||||
Collection<string> IFileSystemWatcherWrapper.Filters
|
||||
{
|
||||
get => this.Filters;
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
foreach (var filter in value)
|
||||
{
|
||||
this.Filters.Add(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
public interface IFileSystemWatcherWrapper
|
||||
{
|
||||
// Events to watch out for
|
||||
event FileSystemEventHandler Created;
|
||||
|
||||
event FileSystemEventHandler Deleted;
|
||||
|
||||
event FileSystemEventHandler Changed;
|
||||
|
||||
event RenamedEventHandler Renamed;
|
||||
|
||||
// Properties of File System watcher
|
||||
Collection<string> Filters { get; set; }
|
||||
|
||||
bool EnableRaisingEvents { get; set; }
|
||||
|
||||
NotifyFilters NotifyFilter { get; set; }
|
||||
|
||||
string Path { get; set; }
|
||||
|
||||
bool IncludeSubdirectories { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
internal interface IProgramRepository
|
||||
{
|
||||
void IndexPrograms();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
public interface IRepository<T>
|
||||
{
|
||||
void Add(T insertedItem);
|
||||
|
||||
void Remove(T removedItem);
|
||||
|
||||
bool Contains(T item);
|
||||
|
||||
void SetList(IList<T> list);
|
||||
|
||||
bool Any();
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// The intent of this class is to provide a basic subset of 'list' like operations, without exposing callers to the internal representation
|
||||
/// of the data structure. Currently this is implemented as a list for it's simplicity.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">typeof</typeparam>
|
||||
public class ListRepository<T> : IRepository<T>, IEnumerable<T>
|
||||
{
|
||||
public IList<T> Items
|
||||
{
|
||||
get { return _items.Values.ToList(); }
|
||||
}
|
||||
|
||||
private ConcurrentDictionary<int, T> _items = new ConcurrentDictionary<int, T>();
|
||||
|
||||
public ListRepository()
|
||||
{
|
||||
}
|
||||
|
||||
public void SetList(IList<T> list)
|
||||
{
|
||||
// enforce that internal representation
|
||||
try
|
||||
{
|
||||
#pragma warning disable CS8602 // Dereference of a possibly null reference.
|
||||
_items = new ConcurrentDictionary<int, T>(list.ToDictionary(i => i.GetHashCode()));
|
||||
#pragma warning restore CS8602 // Dereference of a possibly null reference.
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public bool Any()
|
||||
{
|
||||
return !_items.IsEmpty;
|
||||
}
|
||||
|
||||
public void Add(T insertedItem)
|
||||
{
|
||||
if (insertedItem is not null)
|
||||
{
|
||||
if (!_items.TryAdd(insertedItem.GetHashCode(), insertedItem))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove(T removedItem)
|
||||
{
|
||||
if (removedItem is not null)
|
||||
{
|
||||
if (!_items.TryRemove(removedItem.GetHashCode(), out _))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ParallelQuery<T> AsParallel()
|
||||
{
|
||||
return _items.Values.AsParallel();
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
if (item is not null)
|
||||
{
|
||||
return _items.ContainsKey(item.GetHashCode());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return _items.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _items.GetEnumerator();
|
||||
}
|
||||
|
||||
public int Count()
|
||||
{
|
||||
return _items.Count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
using Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
using Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
using Windows.ApplicationModel;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// A repository for storing packaged applications such as UWP apps or appx packaged desktop apps.
|
||||
/// This repository will also monitor for changes to the PackageCatalog and update the repository accordingly
|
||||
/// </summary>
|
||||
internal sealed class PackageRepository : ListRepository<UWPApplication>, IProgramRepository
|
||||
{
|
||||
private readonly IPackageCatalog _packageCatalog;
|
||||
|
||||
private bool _isDirty;
|
||||
|
||||
public bool ShouldReload()
|
||||
{
|
||||
return _isDirty;
|
||||
}
|
||||
|
||||
public void ResetReloadFlag()
|
||||
{
|
||||
_isDirty = false;
|
||||
}
|
||||
|
||||
// private readonly PluginInitContext _context;
|
||||
public PackageRepository(IPackageCatalog packageCatalog)
|
||||
{
|
||||
_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;
|
||||
_packageCatalog.PackageUpdating += OnPackageUpdating;
|
||||
}
|
||||
|
||||
public void OnPackageInstalling(PackageCatalog p, PackageInstallingEventArgs args)
|
||||
{
|
||||
if (args.IsComplete)
|
||||
{
|
||||
AddPackage(args.Package);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPackageUninstalling(PackageCatalog p, PackageUninstallingEventArgs args)
|
||||
{
|
||||
if (args.Progress == 0)
|
||||
{
|
||||
RemovePackage(args.Package);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnPackageUpdating(PackageCatalog p, PackageUpdatingEventArgs args)
|
||||
{
|
||||
if (args.Progress == 0)
|
||||
{
|
||||
RemovePackage(args.SourcePackage);
|
||||
}
|
||||
|
||||
if (args.IsComplete)
|
||||
{
|
||||
AddPackage(args.TargetPackage);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddPackage(Package package)
|
||||
{
|
||||
var packageWrapper = PackageWrapper.GetWrapperFromPackage(package);
|
||||
if (string.IsNullOrEmpty(packageWrapper.InstalledLocation))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var uwp = new UWP(packageWrapper);
|
||||
uwp.InitializeAppInfo(packageWrapper.InstalledLocation);
|
||||
foreach (var app in uwp.Apps)
|
||||
{
|
||||
app.UpdateLogoPath(ThemeHelper.GetCurrentTheme());
|
||||
Add(app);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeAppInfo will throw if there is no AppxManifest.xml for the package.
|
||||
// Note there are sometimes multiple packages per product and this doesn't necessarily mean that we haven't found the app.
|
||||
// eg. "Could not find file 'C:\\Program Files\\WindowsApps\\Microsoft.WindowsTerminalPreview_2020.616.45.0_neutral_~_8wekyb3d8bbwe\\AppxManifest.xml'."
|
||||
catch (System.IO.FileNotFoundException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void RemovePackage(Package package)
|
||||
{
|
||||
// find apps associated with this package.
|
||||
var packageWrapper = PackageWrapper.GetWrapperFromPackage(package);
|
||||
var uwp = new UWP(packageWrapper);
|
||||
var apps = Items.Where(a => a.Package.Equals(uwp)).ToArray();
|
||||
|
||||
foreach (var app in apps)
|
||||
{
|
||||
Remove(app);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void IndexPrograms()
|
||||
{
|
||||
var windows10 = new Version(10, 0);
|
||||
var support = Environment.OSVersion.Version.Major >= windows10.Major;
|
||||
|
||||
var applications = support ? Programs.UWP.All() : Array.Empty<UWPApplication>();
|
||||
|
||||
SetList(applications);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
internal sealed class Win32ProgramFileSystemWatchers : IDisposable
|
||||
{
|
||||
public string[] PathsToWatch { get; set; }
|
||||
|
||||
public List<FileSystemWatcherWrapper> FileSystemWatchers { get; set; }
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
// This class contains the list of directories to watch and initializes the File System Watchers
|
||||
public Win32ProgramFileSystemWatchers()
|
||||
{
|
||||
PathsToWatch = GetPathsToWatch();
|
||||
|
||||
FileSystemWatchers = new List<FileSystemWatcherWrapper>();
|
||||
for (var index = 0; index < PathsToWatch.Length; index++)
|
||||
{
|
||||
FileSystemWatchers.Add(new FileSystemWatcherWrapper());
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an array of paths to be watched
|
||||
private static string[] GetPathsToWatch()
|
||||
{
|
||||
var paths = new string[]
|
||||
{
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.StartMenu),
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu),
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory),
|
||||
};
|
||||
|
||||
var invalidPaths = new List<string>();
|
||||
foreach (var path in paths)
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.GetFiles(path);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
invalidPaths.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
return paths.Except(invalidPaths).ToArray();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
for (var index = 0; index < PathsToWatch.Length; index++)
|
||||
{
|
||||
FileSystemWatchers[index].Dispose();
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
using Win32Program = Microsoft.CmdPal.Ext.Apps.Programs.Win32Program;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Storage;
|
||||
|
||||
internal sealed class Win32ProgramRepository : ListRepository<Programs.Win32Program>, IProgramRepository
|
||||
{
|
||||
private static readonly IFileSystem FileSystem = new FileSystem();
|
||||
private static readonly IPath Path = FileSystem.Path;
|
||||
|
||||
private const string LnkExtension = ".lnk";
|
||||
private const string UrlExtension = ".url";
|
||||
|
||||
private AllAppsSettings _settings;
|
||||
private IList<IFileSystemWatcherWrapper> _fileSystemWatcherHelpers;
|
||||
private string[] _pathsToWatch;
|
||||
private int _numberOfPathsToWatch;
|
||||
private Collection<string> extensionsToWatch = new Collection<string> { "*.exe", $"*{LnkExtension}", "*.appref-ms", $"*{UrlExtension}" };
|
||||
|
||||
private bool _isDirty;
|
||||
|
||||
private static ConcurrentQueue<string> commonEventHandlingQueue = new ConcurrentQueue<string>();
|
||||
|
||||
public Win32ProgramRepository(IList<IFileSystemWatcherWrapper> fileSystemWatcherHelpers, AllAppsSettings settings, string[] pathsToWatch)
|
||||
{
|
||||
_fileSystemWatcherHelpers = fileSystemWatcherHelpers;
|
||||
_settings = settings ?? throw new ArgumentNullException(nameof(settings), "Win32ProgramRepository requires an initialized settings object");
|
||||
_pathsToWatch = pathsToWatch;
|
||||
_numberOfPathsToWatch = pathsToWatch.Length;
|
||||
InitializeFileSystemWatchers();
|
||||
|
||||
// This task would always run in the background trying to dequeue file paths from the queue at regular intervals.
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var dequeueDelay = 500;
|
||||
var appPath = await EventHandler.GetAppPathFromQueueAsync(commonEventHandlingQueue, dequeueDelay).ConfigureAwait(false);
|
||||
|
||||
// To allow for the installation process to finish.
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(appPath))
|
||||
{
|
||||
Win32Program? app = Win32Program.GetAppFromPath(appPath);
|
||||
if (app != null)
|
||||
{
|
||||
Add(app);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public bool ShouldReload()
|
||||
{
|
||||
return _isDirty;
|
||||
}
|
||||
|
||||
public void ResetReloadFlag()
|
||||
{
|
||||
_isDirty = false;
|
||||
}
|
||||
|
||||
private void InitializeFileSystemWatchers()
|
||||
{
|
||||
for (var index = 0; index < _numberOfPathsToWatch; index++)
|
||||
{
|
||||
// To set the paths to monitor
|
||||
_fileSystemWatcherHelpers[index].Path = _pathsToWatch[index];
|
||||
|
||||
// to be notified when there is a change to a file
|
||||
_fileSystemWatcherHelpers[index].NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;
|
||||
|
||||
// filtering the app types that we want to monitor
|
||||
_fileSystemWatcherHelpers[index].Filters = extensionsToWatch;
|
||||
|
||||
// Registering the event handlers
|
||||
_fileSystemWatcherHelpers[index].Created += OnAppCreated;
|
||||
_fileSystemWatcherHelpers[index].Deleted += OnAppDeleted;
|
||||
_fileSystemWatcherHelpers[index].Renamed += OnAppRenamed;
|
||||
_fileSystemWatcherHelpers[index].Changed += OnAppChanged;
|
||||
|
||||
// Enable the file system watcher
|
||||
_fileSystemWatcherHelpers[index].EnableRaisingEvents = true;
|
||||
|
||||
// Enable it to search in sub folders as well
|
||||
_fileSystemWatcherHelpers[index].IncludeSubdirectories = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnAppRenamedAsync(object sender, RenamedEventArgs e)
|
||||
{
|
||||
var oldPath = e.OldFullPath;
|
||||
var newPath = e.FullPath;
|
||||
|
||||
// fix for https://github.com/microsoft/PowerToys/issues/34391
|
||||
// the msi installer creates a shortcut, which is detected by the PT Run and ends up in calling this OnAppRenamed method
|
||||
// the thread needs to be halted for a short time to avoid locking the new shortcut file as we read it, otherwise the lock causes
|
||||
// in the issue scenario that a warning is popping up during the msi install process.
|
||||
await Task.Delay(1000).ConfigureAwait(false);
|
||||
|
||||
var extension = Path.GetExtension(newPath);
|
||||
Win32Program.ApplicationType oldAppType = Win32Program.GetAppTypeFromPath(oldPath);
|
||||
Programs.Win32Program? newApp = 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.
|
||||
// Therefore, instead of retrieving the old app using the GetAppFromPath(), we construct the application ourself
|
||||
// This situation is not encountered for other application types because the fullPath is the path itself, instead of being computed by using the path to the app.
|
||||
try
|
||||
{
|
||||
if (oldAppType == Win32Program.ApplicationType.ShortcutApplication || oldAppType == Win32Program.ApplicationType.InternetShortcutApplication)
|
||||
{
|
||||
oldApp = new Win32Program() { Name = Path.GetFileNameWithoutExtension(e.OldName) ?? string.Empty, ExecutableName = Path.GetFileName(e.OldName) ?? string.Empty, FullPath = newApp?.FullPath ?? oldPath };
|
||||
}
|
||||
else
|
||||
{
|
||||
oldApp = Win32Program.GetAppFromPath(oldPath);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
// To remove the old app which has been renamed and to add the new application.
|
||||
if (oldApp != null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(oldApp.Name) || string.IsNullOrWhiteSpace(oldApp.ExecutableName) || string.IsNullOrWhiteSpace(oldApp.FullPath))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
Remove(oldApp);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (newApp != null)
|
||||
{
|
||||
Add(newApp);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAppRenamed(object sender, RenamedEventArgs e)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await OnAppRenamedAsync(sender, e).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private void OnAppDeleted(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
var path = e.FullPath;
|
||||
var extension = Path.GetExtension(path);
|
||||
Win32Program? app = null;
|
||||
|
||||
try
|
||||
{
|
||||
// To mitigate the issue of not having a FullPath for a shortcut app, we iterate through the items and find the app with the same hashcode.
|
||||
// Using OrdinalIgnoreCase since this is used internally
|
||||
if (extension.Equals(LnkExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
app = GetAppWithSameLnkFilePath(path);
|
||||
if (app == null)
|
||||
{
|
||||
// Cancelled links won't have a resolved path.
|
||||
app = GetAppWithSameNameAndExecutable(Path.GetFileNameWithoutExtension(path), Path.GetFileName(path));
|
||||
}
|
||||
}
|
||||
else if (extension.Equals(UrlExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
app = GetAppWithSameNameAndExecutable(Path.GetFileNameWithoutExtension(path), Path.GetFileName(path));
|
||||
}
|
||||
else
|
||||
{
|
||||
app = Programs.Win32Program.GetAppFromPath(path);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
if (app != null)
|
||||
{
|
||||
Remove(app);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1309:Use ordinal string comparison", Justification = "Using CurrentCultureIgnoreCase since application names could be dependent on currentculture See: https://github.com/microsoft/PowerToys/pull/5847/files#r468245190")]
|
||||
private Win32Program? GetAppWithSameNameAndExecutable(string name, string executableName)
|
||||
{
|
||||
foreach (Win32Program app in Items)
|
||||
{
|
||||
// Using CurrentCultureIgnoreCase since application names could be dependent on currentculture See: https://github.com/microsoft/PowerToys/pull/5847/files#r468245190
|
||||
if (name.Equals(app.Name, StringComparison.CurrentCultureIgnoreCase) && executableName.Equals(app.ExecutableName, StringComparison.CurrentCultureIgnoreCase))
|
||||
{
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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.Win32Program? GetAppWithSameLnkFilePath(string lnkFilePath)
|
||||
{
|
||||
foreach (Programs.Win32Program app in Items)
|
||||
{
|
||||
// Using Invariant / OrdinalIgnoreCase since we're comparing paths
|
||||
if (lnkFilePath.ToUpperInvariant().Equals(app.LnkFilePath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void OnAppCreated(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
var path = e.FullPath;
|
||||
|
||||
// Using OrdinalIgnoreCase since we're comparing extensions
|
||||
if (!Path.GetExtension(path).Equals(UrlExtension, StringComparison.OrdinalIgnoreCase) && !Path.GetExtension(path).Equals(LnkExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Programs.Win32Program? app = Programs.Win32Program.GetAppFromPath(path);
|
||||
if (app != null)
|
||||
{
|
||||
Add(app);
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAppChanged(object sender, FileSystemEventArgs e)
|
||||
{
|
||||
var path = e.FullPath;
|
||||
|
||||
// Using OrdinalIgnoreCase since we're comparing extensions
|
||||
if (Path.GetExtension(path).Equals(UrlExtension, StringComparison.OrdinalIgnoreCase) || Path.GetExtension(path).Equals(LnkExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// When a url or lnk app is installed, multiple created and changed events are triggered.
|
||||
// To prevent the code from acting on the first such event (which may still be during app installation), the events are added a common queue and dequeued by a background task at regular intervals - https://github.com/microsoft/PowerToys/issues/6429.
|
||||
commonEventHandlingQueue.Enqueue(path);
|
||||
}
|
||||
}
|
||||
|
||||
public void IndexPrograms()
|
||||
{
|
||||
var applications = Programs.Win32Program.All(_settings);
|
||||
|
||||
SetList(applications);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
|
||||
public interface IShellLinkHelper
|
||||
{
|
||||
string RetrieveTargetPath(string path);
|
||||
|
||||
string Description { get; set; }
|
||||
|
||||
string Arguments { get; set; }
|
||||
|
||||
bool HasArguments { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
|
||||
[SuppressMessage("Interoperability", "CA1401:P/Invokes should not be visible", Justification = "We want plugins to share this NativeMethods class, instead of each one creating its own.")]
|
||||
public sealed class Native
|
||||
{
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, nint ppvReserved);
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
public static extern int SHCreateItemFromParsingName([MarshalAs(UnmanagedType.LPWStr)] string path, nint pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem shellItem);
|
||||
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern HRESULT SHCreateStreamOnFileEx(string fileName, STGM grfMode, uint attributes, bool create, System.Runtime.InteropServices.ComTypes.IStream reserved, out System.Runtime.InteropServices.ComTypes.IStream stream);
|
||||
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
public static extern HRESULT SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, nint ppvReserved);
|
||||
|
||||
public enum HRESULT : uint
|
||||
{
|
||||
/// <summary>
|
||||
/// Operation successful.
|
||||
/// </summary>
|
||||
S_OK = 0x00000000,
|
||||
|
||||
/// <summary>
|
||||
/// Operation successful. (negative condition/no operation)
|
||||
/// </summary>
|
||||
S_FALSE = 0x00000001,
|
||||
|
||||
/// <summary>
|
||||
/// Not implemented.
|
||||
/// </summary>
|
||||
E_NOTIMPL = 0x80004001,
|
||||
|
||||
/// <summary>
|
||||
/// No such interface supported.
|
||||
/// </summary>
|
||||
E_NOINTERFACE = 0x80004002,
|
||||
|
||||
/// <summary>
|
||||
/// Pointer that is not valid.
|
||||
/// </summary>
|
||||
E_POINTER = 0x80004003,
|
||||
|
||||
/// <summary>
|
||||
/// Operation aborted.
|
||||
/// </summary>
|
||||
E_ABORT = 0x80004004,
|
||||
|
||||
/// <summary>
|
||||
/// Unspecified failure.
|
||||
/// </summary>
|
||||
E_FAIL = 0x80004005,
|
||||
|
||||
/// <summary>
|
||||
/// Unexpected failure.
|
||||
/// </summary>
|
||||
E_UNEXPECTED = 0x8000FFFF,
|
||||
|
||||
/// <summary>
|
||||
/// General access denied error.
|
||||
/// </summary>
|
||||
E_ACCESSDENIED = 0x80070005,
|
||||
|
||||
/// <summary>
|
||||
/// Handle that is not valid.
|
||||
/// </summary>
|
||||
E_HANDLE = 0x80070006,
|
||||
|
||||
/// <summary>
|
||||
/// Failed to allocate necessary memory.
|
||||
/// </summary>
|
||||
E_OUTOFMEMORY = 0x8007000E,
|
||||
|
||||
/// <summary>
|
||||
/// One or more arguments are not valid.
|
||||
/// </summary>
|
||||
E_INVALIDARG = 0x80070057,
|
||||
|
||||
/// <summary>
|
||||
/// The operation was canceled by the user. (Error source 7 means Win32.)
|
||||
/// </summary>
|
||||
/// <SeeAlso href="https://learn.microsoft.com/windows/win32/debug/system-error-codes--1000-1299-"/>
|
||||
/// <SeeAlso href="https://en.wikipedia.org/wiki/HRESULT"/>
|
||||
E_CANCELLED = 0x800704C7,
|
||||
}
|
||||
|
||||
public static class ShellItemTypeConstants
|
||||
{
|
||||
/// <summary>
|
||||
/// Guid for type IShellItem.
|
||||
/// </summary>
|
||||
public static readonly Guid ShellItemGuid = new("43826d1e-e718-42ee-bc55-a1e261c37bfe");
|
||||
|
||||
/// <summary>
|
||||
/// Guid for type IShellItem2.
|
||||
/// </summary>
|
||||
public static readonly Guid ShellItem2Guid = new("7E9FB0D3-919F-4307-AB2E-9B1860310C93");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The following are ShellItem DisplayName types.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum SIGDN : uint
|
||||
{
|
||||
NORMALDISPLAY = 0,
|
||||
PARENTRELATIVEPARSING = 0x80018001,
|
||||
PARENTRELATIVEFORADDRESSBAR = 0x8001c001,
|
||||
DESKTOPABSOLUTEPARSING = 0x80028000,
|
||||
PARENTRELATIVEEDITING = 0x80031001,
|
||||
DESKTOPABSOLUTEEDITING = 0x8004c000,
|
||||
FILESYSPATH = 0x80058000,
|
||||
URL = 0x80068000,
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")]
|
||||
public interface IShellItem
|
||||
{
|
||||
void BindToHandler(
|
||||
nint pbc,
|
||||
[MarshalAs(UnmanagedType.LPStruct)] Guid bhid,
|
||||
[MarshalAs(UnmanagedType.LPStruct)] Guid riid,
|
||||
out nint ppv);
|
||||
|
||||
void GetParent(out IShellItem ppsi);
|
||||
|
||||
void GetDisplayName(SIGDN sigdnName, [MarshalAs(UnmanagedType.LPWStr)] out string ppszName);
|
||||
|
||||
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
|
||||
|
||||
void Compare(IShellItem psi, uint hint, out int piOrder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see href="https://learn.microsoft.com/windows/win32/stg/stgm-constants">see all STGM values</see>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum STGM : long
|
||||
{
|
||||
READ = 0x00000000L,
|
||||
WRITE = 0x00000001L,
|
||||
READWRITE = 0x00000002L,
|
||||
CREATE = 0x00001000L,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
|
||||
public static class ShellCommand
|
||||
{
|
||||
public enum RunAsType
|
||||
{
|
||||
None,
|
||||
Administrator,
|
||||
OtherUser,
|
||||
}
|
||||
|
||||
public static ProcessStartInfo GetProcessStartInfo(string target, string parentDir, string programArguments, RunAsType runAs = RunAsType.None)
|
||||
{
|
||||
return new ProcessStartInfo
|
||||
{
|
||||
FileName = target,
|
||||
WorkingDirectory = parentDir,
|
||||
UseShellExecute = true,
|
||||
Arguments = programArguments,
|
||||
Verb = runAs == RunAsType.Administrator ? "runAs" : runAs == RunAsType.OtherUser ? "runAsUser" : string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
public static ProcessStartInfo SetProcessStartInfo(this string fileName, string workingDirectory = "", string arguments = "", string verb = "")
|
||||
{
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
FileName = fileName,
|
||||
WorkingDirectory = workingDirectory,
|
||||
Arguments = arguments,
|
||||
Verb = verb,
|
||||
};
|
||||
|
||||
return info;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
|
||||
public class ShellLinkHelper : IShellLinkHelper
|
||||
{
|
||||
[Flags]
|
||||
private enum SLGP_FLAGS
|
||||
{
|
||||
SLGP_SHORTPATH = 0x1,
|
||||
SLGP_UNCPRIORITY = 0x2,
|
||||
SLGP_RAWPATH = 0x4,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching COM")]
|
||||
private struct WIN32_FIND_DATAW
|
||||
{
|
||||
public uint dwFileAttributes;
|
||||
public long ftCreationTime;
|
||||
public long ftLastAccessTime;
|
||||
public long ftLastWriteTime;
|
||||
public uint nFileSizeHigh;
|
||||
public uint nFileSizeLow;
|
||||
public uint dwReserved0;
|
||||
public uint dwReserved1;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||
public string cFileName;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
|
||||
public string cAlternateFileName;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")]
|
||||
public enum SLR_FLAGS
|
||||
{
|
||||
SLR_NO_UI = 0x1,
|
||||
SLR_ANY_MATCH = 0x2,
|
||||
SLR_UPDATE = 0x4,
|
||||
SLR_NOUPDATE = 0x8,
|
||||
SLR_NOSEARCH = 0x10,
|
||||
SLR_NOTRACK = 0x20,
|
||||
SLR_NOLINKINFO = 0x40,
|
||||
SLR_INVOKE_MSI = 0x80,
|
||||
}
|
||||
|
||||
// Reference : http://www.pinvoke.net/default.aspx/Interfaces.IShellLinkW
|
||||
|
||||
// The IShellLink interface allows Shell links to be created, modified, and resolved
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("000214F9-0000-0000-C000-000000000046")]
|
||||
private interface IShellLinkW
|
||||
{
|
||||
/// <summary>Retrieves the path and file name of a Shell link object</summary>
|
||||
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, ref WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags);
|
||||
|
||||
/// <summary>Retrieves the list of item identifiers for a Shell link object</summary>
|
||||
void GetIDList(out nint ppidl);
|
||||
|
||||
/// <summary>Sets the pointer to an item identifier list (PIDL) for a Shell link object.</summary>
|
||||
void SetIDList(nint pidl);
|
||||
|
||||
/// <summary>Retrieves the description string for a Shell link object</summary>
|
||||
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
|
||||
|
||||
/// <summary>Sets the description for a Shell link object. The description can be any application-defined string</summary>
|
||||
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
||||
|
||||
/// <summary>Retrieves the name of the working directory for a Shell link object</summary>
|
||||
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
|
||||
|
||||
/// <summary>Sets the name of the working directory for a Shell link object</summary>
|
||||
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
||||
|
||||
/// <summary>Retrieves the command-line arguments associated with a Shell link object</summary>
|
||||
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
|
||||
|
||||
/// <summary>Sets the command-line arguments for a Shell link object</summary>
|
||||
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
|
||||
|
||||
/// <summary>Retrieves the hot key for a Shell link object</summary>
|
||||
void GetHotkey(out short pwHotkey);
|
||||
|
||||
/// <summary>Sets a hot key for a Shell link object</summary>
|
||||
void SetHotkey(short wHotkey);
|
||||
|
||||
/// <summary>Retrieves the show command for a Shell link object</summary>
|
||||
void GetShowCmd(out int piShowCmd);
|
||||
|
||||
/// <summary>Sets the show command for a Shell link object. The show command sets the initial show state of the window.</summary>
|
||||
void SetShowCmd(int iShowCmd);
|
||||
|
||||
/// <summary>Retrieves the location (path and index) of the icon for a Shell link object</summary>
|
||||
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
|
||||
|
||||
/// <summary>Sets the location (path and index) of the icon for a Shell link object</summary>
|
||||
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
||||
|
||||
/// <summary>Sets the relative path to the Shell link object</summary>
|
||||
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
|
||||
|
||||
/// <summary>Attempts to find the target of a Shell link, even if it has been moved or renamed</summary>
|
||||
void Resolve(ref nint hwnd, SLR_FLAGS fFlags);
|
||||
|
||||
/// <summary>Sets the path and file name of a Shell link object</summary>
|
||||
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("00021401-0000-0000-C000-000000000046")]
|
||||
private class ShellLink
|
||||
{
|
||||
}
|
||||
|
||||
// Contains the description of the app
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
// Contains the arguments to the app
|
||||
public string Arguments { get; set; } = string.Empty;
|
||||
|
||||
public bool HasArguments { get; set; }
|
||||
|
||||
// Retrieve the target path using Shell Link
|
||||
public string RetrieveTargetPath(string path)
|
||||
{
|
||||
var link = new ShellLink();
|
||||
const int STGM_READ = 0;
|
||||
|
||||
try
|
||||
{
|
||||
((IPersistFile)link).Load(path, STGM_READ);
|
||||
}
|
||||
catch (System.IO.FileNotFoundException)
|
||||
{
|
||||
// Log.Exception("Path could not be retrieved", ex, GetType(), path);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var hwnd = default(nint);
|
||||
((IShellLinkW)link).Resolve(ref hwnd, 0);
|
||||
|
||||
const int MAX_PATH = 260;
|
||||
var buffer = new StringBuilder(MAX_PATH);
|
||||
|
||||
var data = default(WIN32_FIND_DATAW);
|
||||
((IShellLinkW)link).GetPath(buffer, buffer.Capacity, ref data, SLGP_FLAGS.SLGP_SHORTPATH);
|
||||
var target = buffer.ToString();
|
||||
|
||||
// To set the app description
|
||||
if (!string.IsNullOrEmpty(target))
|
||||
{
|
||||
buffer = new StringBuilder(MAX_PATH);
|
||||
try
|
||||
{
|
||||
((IShellLinkW)link).GetDescription(buffer, MAX_PATH);
|
||||
Description = buffer.ToString();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Log.Exception($"Failed to fetch description for {target}, {e.Message}", e, GetType());
|
||||
Description = string.Empty;
|
||||
}
|
||||
|
||||
var argumentBuffer = new StringBuilder(MAX_PATH);
|
||||
((IShellLinkW)link).GetArguments(argumentBuffer, argumentBuffer.Capacity);
|
||||
Arguments = argumentBuffer.ToString();
|
||||
|
||||
// Set variable to true if the program takes in any arguments
|
||||
if (argumentBuffer.Length != 0)
|
||||
{
|
||||
HasArguments = true;
|
||||
}
|
||||
}
|
||||
|
||||
// To release unmanaged memory
|
||||
Marshal.ReleaseComObject(link);
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using static Microsoft.CmdPal.Ext.Apps.Utils.Native;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
|
||||
/// <summary>
|
||||
/// Class to get localized name of shell items like 'My computer'. The localization is based on the 'windows display language'.
|
||||
/// </summary>
|
||||
public class ShellLocalization
|
||||
{
|
||||
internal static readonly ShellLocalization Instance = new();
|
||||
|
||||
// Cache for already localized names. This makes localization of already localized string faster.
|
||||
private ConcurrentDictionary<string, string> _localizationCache = new ConcurrentDictionary<string, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the localized name of a shell item.
|
||||
/// </summary>
|
||||
/// <param name="path">Path to the shell item (e. g. shortcut 'File Explorer.lnk').</param>
|
||||
/// <returns>The localized name as string or <see cref="string.Empty"/>.</returns>
|
||||
public string GetLocalizedName(string path)
|
||||
{
|
||||
var lowerInvariantPath = path.ToLowerInvariant();
|
||||
|
||||
// Checking cache if path is already localized
|
||||
if (_localizationCache.TryGetValue(lowerInvariantPath, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
var shellItemType = ShellItemTypeConstants.ShellItemGuid;
|
||||
var retCode = SHCreateItemFromParsingName(path, nint.Zero, ref shellItemType, out var shellItem);
|
||||
if (retCode != 0)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
shellItem.GetDisplayName(SIGDN.NORMALDISPLAY, out var filename);
|
||||
|
||||
_ = _localizationCache.TryAdd(lowerInvariantPath, filename);
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method returns the localized path to a shell item (folder or file)
|
||||
/// </summary>
|
||||
/// <param name="path">The path to localize</param>
|
||||
/// <returns>The localized path or the original path if localized version is not available</returns>
|
||||
public string GetLocalizedPath(string path)
|
||||
{
|
||||
path = Environment.ExpandEnvironmentVariables(path);
|
||||
var ext = Path.GetExtension(path);
|
||||
var pathParts = path.Split("\\");
|
||||
var locPath = new string[pathParts.Length];
|
||||
|
||||
for (var i = 0; i < pathParts.Length; i++)
|
||||
{
|
||||
if (i == 0 && pathParts[i].EndsWith(':'))
|
||||
{
|
||||
// Skip the drive letter.
|
||||
locPath[0] = pathParts[0];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Localize path.
|
||||
var iElements = i + 1;
|
||||
var lName = GetLocalizedName(string.Join("\\", pathParts[..iElements]));
|
||||
locPath[i] = !string.IsNullOrEmpty(lName) ? lName : pathParts[i];
|
||||
}
|
||||
|
||||
var newPath = string.Join("\\", locPath);
|
||||
newPath = !newPath.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase) ? newPath + ext : newPath;
|
||||
|
||||
return newPath;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
|
||||
public enum Theme
|
||||
{
|
||||
System,
|
||||
Light,
|
||||
Dark,
|
||||
HighContrastOne,
|
||||
HighContrastTwo,
|
||||
HighContrastBlack,
|
||||
HighContrastWhite,
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Utils;
|
||||
|
||||
public static class ThemeHelper
|
||||
{
|
||||
public static Theme GetCurrentTheme()
|
||||
{
|
||||
// Check for high-contrast mode
|
||||
Theme highContrastTheme = GetHighContrastBaseType();
|
||||
if (highContrastTheme != Theme.Light)
|
||||
{
|
||||
return highContrastTheme;
|
||||
}
|
||||
|
||||
// Check if the system is using dark or light mode
|
||||
return IsSystemDarkMode() ? Theme.Dark : Theme.Light;
|
||||
}
|
||||
|
||||
private static bool IsSystemDarkMode()
|
||||
{
|
||||
const string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
|
||||
const string registryValue = "AppsUseLightTheme";
|
||||
|
||||
// Retrieve the registry value, which is a DWORD (0 or 1)
|
||||
var registryValueObj = Registry.GetValue(registryKey, registryValue, null);
|
||||
if (registryValueObj != null)
|
||||
{
|
||||
// 0 = Dark mode, 1 = Light mode
|
||||
var isLightMode = Convert.ToBoolean((int)registryValueObj, CultureInfo.InvariantCulture);
|
||||
return !isLightMode; // Invert because 0 = Dark
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to Light theme if the registry key is missing
|
||||
return false; // Default to dark mode assumption
|
||||
}
|
||||
}
|
||||
|
||||
public static Theme GetHighContrastBaseType()
|
||||
{
|
||||
const string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes";
|
||||
const string registryValue = "CurrentTheme";
|
||||
|
||||
var themePath = (string?)Registry.GetValue(registryKey, registryValue, string.Empty);
|
||||
if (string.IsNullOrEmpty(themePath))
|
||||
{
|
||||
return Theme.Light; // Default to light theme if missing
|
||||
}
|
||||
|
||||
var theme = themePath.Split('\\').Last().Split('.').First().ToLowerInvariant();
|
||||
|
||||
return theme switch
|
||||
{
|
||||
"hc1" => Theme.HighContrastOne,
|
||||
"hc2" => Theme.HighContrastTwo,
|
||||
"hcwhite" => Theme.HighContrastWhite,
|
||||
"hcblack" => Theme.HighContrastBlack,
|
||||
_ => Theme.Light,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.CmdPal.Ext.Bookmarks.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
internal sealed partial class OpenInTerminalCommand : InvokableCommand
|
||||
{
|
||||
private readonly string _folder;
|
||||
|
||||
public OpenInTerminalCommand(string folder)
|
||||
{
|
||||
Name = Resources.bookmarks_open_in_terminal_name;
|
||||
_folder = folder;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Start Windows Terminal with the specified folder
|
||||
var startInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "wt.exe",
|
||||
Arguments = $"-d \"{_folder}\"",
|
||||
UseShellExecute = true,
|
||||
};
|
||||
System.Diagnostics.Process.Start(startInfo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error launching Windows Terminal: {ex.Message}");
|
||||
}
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
189
src/modules/cmdpal/exts/Microsoft.CmdPal.Ext.Bookmark/Properties/Resources.Designer.cs
generated
Normal file
189
src/modules/cmdpal/exts/Microsoft.CmdPal.Ext.Bookmark/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,189 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Ext.Bookmarks.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Add bookmark.
|
||||
/// </summary>
|
||||
public static string bookmarks_add_name {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_add_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Add a bookmark.
|
||||
/// </summary>
|
||||
public static string bookmarks_add_title {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_add_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Delete.
|
||||
/// </summary>
|
||||
public static string bookmarks_delete_name {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_delete_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Delete bookmark.
|
||||
/// </summary>
|
||||
public static string bookmarks_delete_title {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_delete_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bookmarks.
|
||||
/// </summary>
|
||||
public static string bookmarks_display_name {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_display_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Edit bookmark.
|
||||
/// </summary>
|
||||
public static string bookmarks_edit_name {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_edit_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to URL or file path.
|
||||
/// </summary>
|
||||
public static string bookmarks_form_bookmark_label {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_form_bookmark_label", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to URL or file path is required.
|
||||
/// </summary>
|
||||
public static string bookmarks_form_bookmark_required {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_form_bookmark_required", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Name.
|
||||
/// </summary>
|
||||
public static string bookmarks_form_name_label {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_form_name_label", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Name is required.
|
||||
/// </summary>
|
||||
public static string bookmarks_form_name_required {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_form_name_required", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open.
|
||||
/// </summary>
|
||||
public static string bookmarks_form_open {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_form_open", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Save.
|
||||
/// </summary>
|
||||
public static string bookmarks_form_save {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_form_save", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open in Terminal.
|
||||
/// </summary>
|
||||
public static string bookmarks_open_in_terminal_name {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_open_in_terminal_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to {0} is required.
|
||||
/// </summary>
|
||||
public static string bookmarks_required_placeholder {
|
||||
get {
|
||||
return ResourceManager.GetString("bookmarks_required_placeholder", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="bookmarks_display_name" xml:space="preserve">
|
||||
<value>Bookmarks</value>
|
||||
</data>
|
||||
<data name="bookmarks_add_title" xml:space="preserve">
|
||||
<value>Add a bookmark</value>
|
||||
</data>
|
||||
<data name="bookmarks_edit_name" xml:space="preserve">
|
||||
<value>Edit bookmark</value>
|
||||
</data>
|
||||
<data name="bookmarks_add_name" xml:space="preserve">
|
||||
<value>Add bookmark</value>
|
||||
</data>
|
||||
<data name="bookmarks_delete_title" xml:space="preserve">
|
||||
<value>Delete bookmark</value>
|
||||
</data>
|
||||
<data name="bookmarks_delete_name" xml:space="preserve">
|
||||
<value>Delete</value>
|
||||
</data>
|
||||
<data name="bookmarks_open_in_terminal_name" xml:space="preserve">
|
||||
<value>Open in Terminal</value>
|
||||
<comment>"Terminal" should be the localized name of the Windows Terminal</comment>
|
||||
</data>
|
||||
<data name="bookmarks_form_name_label" xml:space="preserve">
|
||||
<value>Name</value>
|
||||
</data>
|
||||
<data name="bookmarks_form_save" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
<data name="bookmarks_form_open" xml:space="preserve">
|
||||
<value>Open</value>
|
||||
</data>
|
||||
<data name="bookmarks_form_name_required" xml:space="preserve">
|
||||
<value>Name is required</value>
|
||||
</data>
|
||||
<data name="bookmarks_form_bookmark_label" xml:space="preserve">
|
||||
<value>URL or file path</value>
|
||||
</data>
|
||||
<data name="bookmarks_form_bookmark_required" xml:space="preserve">
|
||||
<value>URL or file path is required</value>
|
||||
</data>
|
||||
<data name="bookmarks_required_placeholder" xml:space="preserve">
|
||||
<value>{0} is required</value>
|
||||
<comment>{0} will be replaced by a parameter name provided by the user</comment>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,98 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
public partial class UrlCommand : InvokableCommand
|
||||
{
|
||||
public string Type { get; }
|
||||
|
||||
public string Url { get; }
|
||||
|
||||
public UrlCommand(BookmarkData data)
|
||||
: this(data.Name, data.Bookmark, data.Type)
|
||||
{
|
||||
}
|
||||
|
||||
public UrlCommand(string name, string url, string type)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
Url = url;
|
||||
Icon = new IconInfo(IconFromUrl(Url, type));
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
var target = Url;
|
||||
try
|
||||
{
|
||||
var uri = GetUri(target);
|
||||
if (uri != null)
|
||||
{
|
||||
_ = Launcher.LaunchUriAsync(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// throw new UriFormatException("The provided URL is not valid.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error launching URL: {ex.Message}");
|
||||
}
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
|
||||
internal static Uri? GetUri(string url)
|
||||
{
|
||||
Uri? uri;
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out uri))
|
||||
{
|
||||
if (!Uri.TryCreate("https://" + url, UriKind.Absolute, out uri))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
internal static string IconFromUrl(string url, string type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case "file":
|
||||
return "📄";
|
||||
case "folder":
|
||||
return "📁";
|
||||
case "web":
|
||||
default:
|
||||
// Get the base url up to the first placeholder
|
||||
var placeholderIndex = url.IndexOf('{');
|
||||
var baseString = placeholderIndex > 0 ? url.Substring(0, placeholderIndex) : url;
|
||||
try
|
||||
{
|
||||
var uri = GetUri(baseString);
|
||||
if (uri != null)
|
||||
{
|
||||
var hostname = uri.Host;
|
||||
var faviconUrl = $"{uri.Scheme}://{hostname}/favicon.ico";
|
||||
return faviconUrl;
|
||||
}
|
||||
}
|
||||
catch (UriFormatException)
|
||||
{
|
||||
// return "🔗";
|
||||
}
|
||||
|
||||
return "🔗";
|
||||
}
|
||||
}
|
||||
}
|
||||
126
src/modules/cmdpal/exts/Microsoft.CmdPal.Ext.Calc/Properties/Resources.Designer.cs
generated
Normal file
126
src/modules/cmdpal/exts/Microsoft.CmdPal.Ext.Calc/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,126 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Calc.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Ext.Calc.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Copy.
|
||||
/// </summary>
|
||||
public static string calculator_copy_command_name {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_copy_command_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Calculator.
|
||||
/// </summary>
|
||||
public static string calculator_display_name {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_display_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Error: {0}.
|
||||
/// </summary>
|
||||
public static string calculator_error {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_error", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Type an equation....
|
||||
/// </summary>
|
||||
public static string calculator_placeholder_text {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_placeholder_text", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Save.
|
||||
/// </summary>
|
||||
public static string calculator_save_command_name {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_save_command_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Calculator.
|
||||
/// </summary>
|
||||
public static string calculator_title {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Press = to type an equation.
|
||||
/// </summary>
|
||||
public static string calculator_top_level_subtitle {
|
||||
get {
|
||||
return ResourceManager.GetString("calculator_top_level_subtitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="calculator_display_name" xml:space="preserve">
|
||||
<value>Calculator</value>
|
||||
</data>
|
||||
<data name="calculator_title" xml:space="preserve">
|
||||
<value>Calculator</value>
|
||||
</data>
|
||||
<data name="calculator_top_level_subtitle" xml:space="preserve">
|
||||
<value>Press = to type an equation</value>
|
||||
<comment>{Locked="="}</comment>
|
||||
</data>
|
||||
<data name="calculator_placeholder_text" xml:space="preserve">
|
||||
<value>Type an equation...</value>
|
||||
</data>
|
||||
<data name="calculator_error" xml:space="preserve">
|
||||
<value>Error: {0}</value>
|
||||
<comment>{0} will be replaced by an error message from an invalid equation</comment>
|
||||
</data>
|
||||
<data name="calculator_save_command_name" xml:space="preserve">
|
||||
<value>Save</value>
|
||||
</data>
|
||||
<data name="calculator_copy_command_name" xml:space="preserve">
|
||||
<value>Copy</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.IO;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Data;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer;
|
||||
|
||||
internal sealed partial class FallbackOpenFileItem : FallbackCommandItem
|
||||
{
|
||||
public FallbackOpenFileItem()
|
||||
: base(new NoOpCommand(), Resources.Indexer_Find_Path_fallback_display_title)
|
||||
{
|
||||
Title = string.Empty;
|
||||
Subtitle = string.Empty;
|
||||
}
|
||||
|
||||
public override void UpdateQuery(string query)
|
||||
{
|
||||
if (Path.Exists(query))
|
||||
{
|
||||
var item = new IndexerItem() { FullPath = query, FileName = Path.GetFileName(query) };
|
||||
var listItemForUs = new IndexerListItem(item, IncludeBrowseCommand.AsDefault);
|
||||
Command = listItemForUs.Command;
|
||||
MoreCommands = listItemForUs.MoreCommands;
|
||||
Subtitle = item.FileName;
|
||||
Title = item.FullPath;
|
||||
Icon = listItemForUs.Icon;
|
||||
|
||||
try
|
||||
{
|
||||
var stream = ThumbnailHelper.GetThumbnail(item.FullPath).Result;
|
||||
if (stream != null)
|
||||
{
|
||||
var data = new IconData(RandomAccessStreamReference.CreateFromStream(stream));
|
||||
Icon = new IconInfo(data, data);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Title = string.Empty;
|
||||
Subtitle = string.Empty;
|
||||
Command = new NoOpCommand();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Indexer.OleDB;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Indexer.Utils;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Native;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.System.Com;
|
||||
using Windows.Win32.System.Search;
|
||||
using Windows.Win32.UI.Shell.PropertiesSystem;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Indexer;
|
||||
|
||||
internal sealed partial class SearchQuery : IDisposable
|
||||
{
|
||||
private readonly Lock _lockObject = new(); // Lock object for synchronization
|
||||
private readonly DBPROPIDSET dbPropIdSet;
|
||||
|
||||
private uint reuseWhereID;
|
||||
private EventWaitHandle queryCompletedEvent;
|
||||
private Timer queryTpTimer;
|
||||
private IRowset currentRowset;
|
||||
private IRowset reuseRowset;
|
||||
|
||||
public uint Cookie { get; set; }
|
||||
|
||||
public string SearchText { get; private set; }
|
||||
|
||||
public ConcurrentQueue<SearchResult> SearchResults { get; private set; } = [];
|
||||
|
||||
public SearchQuery()
|
||||
{
|
||||
dbPropIdSet = new DBPROPIDSET
|
||||
{
|
||||
rgPropertyIDs = Marshal.AllocCoTaskMem(sizeof(uint)), // Allocate memory for the property ID array
|
||||
cPropertyIDs = 1,
|
||||
guidPropertySet = new Guid("AA6EE6B0-E828-11D0-B23E-00AA0047FC01"), // DBPROPSET_MSIDXS_ROWSETEXT,
|
||||
};
|
||||
|
||||
// Copy the property ID into the allocated memory
|
||||
Marshal.WriteInt32(dbPropIdSet.rgPropertyIDs, 8); // MSIDXSPROP_WHEREID
|
||||
|
||||
Init();
|
||||
}
|
||||
|
||||
private void Init()
|
||||
{
|
||||
// Create all the objects we will want cached
|
||||
try
|
||||
{
|
||||
queryTpTimer = new Timer(QueryTimerCallback, this, Timeout.Infinite, Timeout.Infinite);
|
||||
if (queryTpTimer == null)
|
||||
{
|
||||
Logger.LogError("Failed to create query timer");
|
||||
return;
|
||||
}
|
||||
|
||||
queryCompletedEvent = new EventWaitHandle(false, EventResetMode.ManualReset);
|
||||
if (queryCompletedEvent == null)
|
||||
{
|
||||
Logger.LogError("Failed to create query completed event");
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute a synchronous query on file items to prime the index and keep that handle around
|
||||
PrimeIndexAndCacheWhereId();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Exception at SearchUXQueryHelper Init", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void WaitForQueryCompletedEvent() => queryCompletedEvent.WaitOne();
|
||||
|
||||
public void CancelOutstandingQueries()
|
||||
{
|
||||
Logger.LogDebug("Cancel query " + SearchText);
|
||||
|
||||
// Are we currently doing work? If so, let's cancel
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (queryTpTimer != null)
|
||||
{
|
||||
queryTpTimer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
queryTpTimer.Dispose();
|
||||
queryTpTimer = null;
|
||||
}
|
||||
|
||||
Init();
|
||||
}
|
||||
}
|
||||
|
||||
public void Execute(string searchText, uint cookie)
|
||||
{
|
||||
SearchText = searchText;
|
||||
Cookie = cookie;
|
||||
ExecuteSyncInternal();
|
||||
}
|
||||
|
||||
public static void QueryTimerCallback(object state)
|
||||
{
|
||||
var pQueryHelper = (SearchQuery)state;
|
||||
pQueryHelper.ExecuteSyncInternal();
|
||||
}
|
||||
|
||||
private void ExecuteSyncInternal()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
var queryStr = QueryStringBuilder.GenerateQuery(SearchText, reuseWhereID);
|
||||
try
|
||||
{
|
||||
// We need to generate a search query string with the search text the user entered above
|
||||
if (currentRowset != null)
|
||||
{
|
||||
if (reuseRowset != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(reuseRowset);
|
||||
}
|
||||
|
||||
// We have a previous rowset, this means the user is typing and we should store this
|
||||
// recapture the where ID from this so the next ExecuteSync call will be faster
|
||||
reuseRowset = currentRowset;
|
||||
reuseWhereID = GetReuseWhereId(reuseRowset);
|
||||
}
|
||||
|
||||
currentRowset = ExecuteCommand(queryStr);
|
||||
|
||||
SearchResults.Clear();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error executing query", ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
queryCompletedEvent.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool HandleRow(IGetRow getRow, nuint rowHandle)
|
||||
{
|
||||
object propertyStorePtr = null;
|
||||
|
||||
try
|
||||
{
|
||||
getRow.GetRowFromHROW(null, rowHandle, typeof(IPropertyStore).GUID, out propertyStorePtr);
|
||||
|
||||
var propertyStore = (IPropertyStore)propertyStorePtr;
|
||||
if (propertyStore == null)
|
||||
{
|
||||
Logger.LogError("Failed to get IPropertyStore interface");
|
||||
return false;
|
||||
}
|
||||
|
||||
var searchResult = SearchResult.Create(propertyStore);
|
||||
if (searchResult == null)
|
||||
{
|
||||
Logger.LogError("Failed to create search result");
|
||||
return false;
|
||||
}
|
||||
|
||||
SearchResults.Enqueue(searchResult);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error handling row", ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Ensure the COM object is released if not returned
|
||||
if (propertyStorePtr != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(propertyStorePtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool FetchRows(int offset, int limit)
|
||||
{
|
||||
if (currentRowset == null)
|
||||
{
|
||||
Logger.LogError("No rowset to fetch rows from");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentRowset is not IGetRow)
|
||||
{
|
||||
Logger.LogInfo("Reset the current rowset");
|
||||
ExecuteSyncInternal();
|
||||
}
|
||||
|
||||
if (currentRowset is not IGetRow getRow)
|
||||
{
|
||||
Logger.LogError("Rowset does not support IGetRow interface");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint rowCountReturned;
|
||||
var prghRows = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
var res = currentRowset.GetNextRows(IntPtr.Zero, offset, limit, out rowCountReturned, out prghRows);
|
||||
if (res < 0)
|
||||
{
|
||||
Logger.LogError($"Error fetching rows: {res}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rowCountReturned == 0)
|
||||
{
|
||||
// No more rows to fetch
|
||||
return false;
|
||||
}
|
||||
|
||||
// Marshal the row handles
|
||||
var rowHandles = new IntPtr[rowCountReturned];
|
||||
Marshal.Copy(prghRows, rowHandles, 0, (int)rowCountReturned);
|
||||
|
||||
for (var i = 0; i < rowCountReturned; i++)
|
||||
{
|
||||
var rowHandle = Marshal.ReadIntPtr(prghRows, i * IntPtr.Size);
|
||||
if (!HandleRow(getRow, (nuint)rowHandle))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
res = currentRowset.ReleaseRows(rowCountReturned, rowHandles, IntPtr.Zero, null, null);
|
||||
if (res != 0)
|
||||
{
|
||||
Logger.LogError($"Error releasing rows: {res}");
|
||||
}
|
||||
|
||||
Marshal.FreeCoTaskMem(prghRows);
|
||||
prghRows = IntPtr.Zero;
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Error fetching rows", ex);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (prghRows != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeCoTaskMem(prghRows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PrimeIndexAndCacheWhereId()
|
||||
{
|
||||
var queryStr = QueryStringBuilder.GeneratePrimingQuery();
|
||||
var rowset = ExecuteCommand(queryStr);
|
||||
if (rowset != null)
|
||||
{
|
||||
if (reuseRowset != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(reuseRowset);
|
||||
}
|
||||
|
||||
reuseRowset = rowset;
|
||||
reuseWhereID = GetReuseWhereId(reuseRowset);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe IRowset ExecuteCommand(string queryStr)
|
||||
{
|
||||
object sessionPtr = null;
|
||||
object commandPtr = null;
|
||||
|
||||
try
|
||||
{
|
||||
var session = (IDBCreateSession)DataSourceManager.GetDataSource();
|
||||
session.CreateSession(null, typeof(IDBCreateCommand).GUID, out sessionPtr);
|
||||
if (sessionPtr == null)
|
||||
{
|
||||
Logger.LogError("CreateSession failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
var createCommand = (IDBCreateCommand)sessionPtr;
|
||||
createCommand.CreateCommand(null, typeof(ICommandText).GUID, out commandPtr);
|
||||
if (commandPtr == null)
|
||||
{
|
||||
Logger.LogError("CreateCommand failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
var commandText = (ICommandText)commandPtr;
|
||||
if (commandText == null)
|
||||
{
|
||||
Logger.LogError("Failed to get ICommandText interface");
|
||||
return null;
|
||||
}
|
||||
|
||||
commandText.SetCommandText(in NativeHelpers.OleDb.DbGuidDefault, queryStr);
|
||||
commandText.Execute(null, typeof(IRowset).GUID, null, null, out var rowsetPointer);
|
||||
|
||||
return rowsetPointer as IRowset;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError("Unexpected error.", ex);
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Release the command pointer
|
||||
if (commandPtr != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(commandPtr);
|
||||
}
|
||||
|
||||
// Release the session pointer
|
||||
if (sessionPtr != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(sessionPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IRowsetInfo GetRowsetInfo(IRowset rowset)
|
||||
{
|
||||
if (rowset == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var rowsetPtr = IntPtr.Zero;
|
||||
var rowsetInfoPtr = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
// Get the IUnknown pointer for the IRowset object
|
||||
rowsetPtr = Marshal.GetIUnknownForObject(rowset);
|
||||
|
||||
// Query for IRowsetInfo interface
|
||||
var rowsetInfoGuid = typeof(IRowsetInfo).GUID;
|
||||
var res = Marshal.QueryInterface(rowsetPtr, in rowsetInfoGuid, out rowsetInfoPtr);
|
||||
if (res != 0)
|
||||
{
|
||||
Logger.LogError($"Error getting IRowsetInfo interface: {res}");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Marshal the interface pointer to the actual IRowsetInfo object
|
||||
var rowsetInfo = (IRowsetInfo)Marshal.GetObjectForIUnknown(rowsetInfoPtr);
|
||||
return rowsetInfo;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Exception occurred while getting IRowsetInfo. ", ex);
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Release the IRowsetInfo pointer if it was obtained
|
||||
if (rowsetInfoPtr != IntPtr.Zero)
|
||||
{
|
||||
Marshal.Release(rowsetInfoPtr); // Release the IRowsetInfo pointer
|
||||
}
|
||||
|
||||
// Release the IUnknown pointer for the IRowset object
|
||||
if (rowsetPtr != IntPtr.Zero)
|
||||
{
|
||||
Marshal.Release(rowsetPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private DBPROP? GetPropset(IRowsetInfo rowsetInfo)
|
||||
{
|
||||
var prgPropSetsPtr = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
ulong cPropertySets;
|
||||
var res = rowsetInfo.GetProperties(1, [dbPropIdSet], out cPropertySets, out prgPropSetsPtr);
|
||||
if (res != 0)
|
||||
{
|
||||
Logger.LogError($"Error getting properties: {res}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cPropertySets == 0 || prgPropSetsPtr == IntPtr.Zero)
|
||||
{
|
||||
Logger.LogError("No property sets returned");
|
||||
return null;
|
||||
}
|
||||
|
||||
var firstPropSetPtr = new IntPtr(prgPropSetsPtr.ToInt64());
|
||||
var propSet = Marshal.PtrToStructure<DBPROPSET>(firstPropSetPtr);
|
||||
if (propSet.cProperties == 0 || propSet.rgProperties == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var propPtr = new IntPtr(propSet.rgProperties.ToInt64());
|
||||
var prop = Marshal.PtrToStructure<DBPROP>(propPtr);
|
||||
return prop;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Exception occurred while getting properties,", ex);
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Free the property sets pointer returned by GetProperties, if necessary
|
||||
if (prgPropSetsPtr != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeCoTaskMem(prgPropSetsPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private uint GetReuseWhereId(IRowset rowset)
|
||||
{
|
||||
var rowsetInfo = GetRowsetInfo(rowset);
|
||||
if (rowsetInfo == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var prop = GetPropset(rowsetInfo);
|
||||
if (prop == null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (prop?.vValue.Anonymous.Anonymous.vt == VARENUM.VT_UI4)
|
||||
{
|
||||
var value = prop?.vValue.Anonymous.Anonymous.Anonymous.ulVal;
|
||||
return (uint)value;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CancelOutstandingQueries();
|
||||
|
||||
// Free the allocated memory for rgPropertyIDs
|
||||
if (dbPropIdSet.rgPropertyIDs != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeCoTaskMem(dbPropIdSet.rgPropertyIDs);
|
||||
}
|
||||
|
||||
if (reuseRowset != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(reuseRowset);
|
||||
reuseRowset = null;
|
||||
}
|
||||
|
||||
if (currentRowset != null)
|
||||
{
|
||||
Marshal.ReleaseComObject(currentRowset);
|
||||
currentRowset = null;
|
||||
}
|
||||
|
||||
queryCompletedEvent?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch;
|
||||
|
||||
[CoClass(typeof(CSearchCatalogManagerClass))]
|
||||
[Guid("AB310581-AC80-11D1-8DF3-00C04FB6EF50")]
|
||||
[ComImport]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1715:Identifiers should have correct prefix", Justification = "Using original name from type lib")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1302:Interface names should begin with I", Justification = "Using original name from type lib")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Using original name from type lib")]
|
||||
public interface CSearchCatalogManager : ISearchCatalogManager
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch;
|
||||
|
||||
[ComConversionLoss]
|
||||
[Guid("AAB49DD5-AD0B-40AE-B654-AE8976BF6BD2")]
|
||||
[ClassInterface((short)0)]
|
||||
[ComImport]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:Property accessors should follow order", Justification = "The order of the property accessors must match the order in which the methods were defined in the vtable")]
|
||||
public class CSearchCatalogManagerClass : ISearchCatalogManager, CSearchCatalogManager
|
||||
{
|
||||
[DispId(1610678272)]
|
||||
public virtual extern string Name
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern IntPtr GetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void SetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName, [In] ref object pValue);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void GetCatalogStatus(
|
||||
out object pStatus,
|
||||
out object pPausedReason);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void Reset();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void Reindex();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void ReindexMatchingURLs([MarshalAs(UnmanagedType.LPWStr), In] string pszPattern);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void ReindexSearchRoot([MarshalAs(UnmanagedType.LPWStr), In] string pszRoot);
|
||||
|
||||
[DispId(1610678280)]
|
||||
public virtual extern uint ConnectTimeout
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678282)]
|
||||
public virtual extern uint DataTimeout
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern int NumberOfItems();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void NumberOfItemsToIndex(
|
||||
out int plIncrementalCount,
|
||||
out int plNotificationQueue,
|
||||
out int plHighPriorityQueue);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
public virtual extern string URLBeingIndexed();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern uint GetURLIndexingState([MarshalAs(UnmanagedType.LPWStr), In] string psz);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.Interface)]
|
||||
public virtual extern object GetPersistentItemsChangedSink();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void RegisterViewForNotification(
|
||||
[MarshalAs(UnmanagedType.LPWStr), In] string pszView,
|
||||
[MarshalAs(UnmanagedType.Interface), In] object pViewChangedSink,
|
||||
out uint pdwCookie);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void GetItemsChangedSink(
|
||||
[MarshalAs(UnmanagedType.Interface), In] object pISearchNotifyInlineSite,
|
||||
[In] ref Guid riid,
|
||||
out IntPtr ppv,
|
||||
out Guid pGUIDCatalogResetSignature,
|
||||
out Guid pGUIDCheckPointSignature,
|
||||
out uint pdwLastCheckPointNumber);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void UnregisterViewForNotification([In] uint dwCookie);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void SetExtensionClusion([MarshalAs(UnmanagedType.LPWStr), In] string pszExtension, [In] int fExclude);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.Interface)]
|
||||
public virtual extern object EnumerateExcludedExtensions();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.Interface)]
|
||||
public virtual extern CSearchQueryHelper GetQueryHelper();
|
||||
|
||||
[DispId(1610678295)]
|
||||
public virtual extern int DiacriticSensitivity
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.Interface)]
|
||||
public virtual extern object GetCrawlScopeManager();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch;
|
||||
|
||||
[CoClass(typeof(CSearchManagerClass))]
|
||||
[Guid("AB310581-AC80-11D1-8DF3-00C04FB6EF69")]
|
||||
[ComImport]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1715:Identifiers should have correct prefix", Justification = "Using original name from type lib")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1302:Interface names should begin with I", Justification = "Using original name from type lib")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Using original name from type lib")]
|
||||
public interface CSearchManager : ISearchManager
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch;
|
||||
|
||||
[Guid("7D096C5F-AC08-4F1F-BEB7-5C22C517CE39")]
|
||||
[TypeLibType(2)]
|
||||
[ClassInterface((short)0)]
|
||||
[ComConversionLoss]
|
||||
[ComImport]
|
||||
public class CSearchManagerClass : ISearchManager, CSearchManager
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void GetIndexerVersionStr([MarshalAs(UnmanagedType.LPWStr)] out string ppszVersionString);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void GetIndexerVersion(out uint pdwMajor, out uint pdwMinor);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern IntPtr GetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void SetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName, [In] ref object pValue);
|
||||
|
||||
[DispId(1610678276)]
|
||||
public virtual extern string ProxyName
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678277)]
|
||||
public virtual extern string BypassList
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void SetProxy(
|
||||
[In] object sUseProxy,
|
||||
[In] int fLocalByPassProxy,
|
||||
[In] uint dwPortNumber,
|
||||
[MarshalAs(UnmanagedType.LPWStr), In] string pszProxyName,
|
||||
[MarshalAs(UnmanagedType.LPWStr), In] string pszByPassList);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.Interface)]
|
||||
public virtual extern CSearchCatalogManager GetCatalog([MarshalAs(UnmanagedType.LPWStr), In] string pszCatalog);
|
||||
|
||||
[DispId(1610678280)]
|
||||
public virtual extern string UserAgent
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: MarshalAs(UnmanagedType.LPWStr)]
|
||||
[param: In]
|
||||
set;
|
||||
}
|
||||
|
||||
[DispId(1610678282)]
|
||||
public virtual extern object UseProxy
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678283)]
|
||||
public virtual extern int LocalBypass
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678284)]
|
||||
public virtual extern uint PortNumber
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch;
|
||||
|
||||
[Guid("AB310581-AC80-11D1-8DF3-00C04FB6EF63")]
|
||||
[CoClass(typeof(CSearchQueryHelperClass))]
|
||||
[ComImport]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1715:Identifiers should have correct prefix", Justification = "Using original name from type lib")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1302:Interface names should begin with I", Justification = "Using original name from type lib")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Using original name from type lib")]
|
||||
public interface CSearchQueryHelper : ISearchQueryHelper
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch;
|
||||
|
||||
[ClassInterface((short)0)]
|
||||
[Guid("B271E955-09E1-42E1-9B95-5994A534B613")]
|
||||
[ComImport]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:Property accessors should follow order", Justification = "The order of the property accessors must match the order in which the methods were defined in the vtable")]
|
||||
public class CSearchQueryHelperClass : ISearchQueryHelper, CSearchQueryHelper
|
||||
{
|
||||
[DispId(1610678272)]
|
||||
public virtual extern string ConnectionString
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678273)]
|
||||
public virtual extern uint QueryContentLocale
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678275)]
|
||||
public virtual extern uint QueryKeywordLocale
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678277)]
|
||||
public virtual extern object QueryTermExpansion
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678279)]
|
||||
public virtual extern object QuerySyntax
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678281)]
|
||||
public virtual extern string QueryContentProperties
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: MarshalAs(UnmanagedType.LPWStr)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678283)]
|
||||
public virtual extern string QuerySelectColumns
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: MarshalAs(UnmanagedType.LPWStr)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678285)]
|
||||
public virtual extern string QueryWhereRestrictions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: MarshalAs(UnmanagedType.LPWStr)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678287)]
|
||||
public virtual extern string QuerySorting
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: MarshalAs(UnmanagedType.LPWStr)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
public virtual extern string GenerateSQLFromUserQuery([MarshalAs(UnmanagedType.LPWStr), In] string pszQuery);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public virtual extern void WriteProperties(
|
||||
[In] int itemID,
|
||||
[In] uint dwNumberOfColumns,
|
||||
[In] ref object pColumns,
|
||||
[In] ref object pValues,
|
||||
[In] ref object pftGatherModifiedTime);
|
||||
|
||||
[DispId(1610678291)]
|
||||
public virtual extern int QueryMaxResults
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch;
|
||||
|
||||
[Guid("AB310581-AC80-11D1-8DF3-00C04FB6EF50")]
|
||||
[ComConversionLoss]
|
||||
[InterfaceType(1)]
|
||||
[ComImport]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:Property accessors should follow order", Justification = "The order of the property accessors must match the order in which the methods were defined in the vtable")]
|
||||
public interface ISearchCatalogManager
|
||||
{
|
||||
[DispId(1610678272)]
|
||||
string Name
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
IntPtr GetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void SetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName, [In] ref object pValue);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetCatalogStatus(out object pStatus, out object pPausedReason);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void Reset();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void Reindex();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void ReindexMatchingURLs([MarshalAs(UnmanagedType.LPWStr), In] string pszPattern);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void ReindexSearchRoot([MarshalAs(UnmanagedType.LPWStr), In] string pszRoot);
|
||||
|
||||
[DispId(1610678280)]
|
||||
uint ConnectTimeout
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678282)]
|
||||
uint DataTimeout
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
int NumberOfItems();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void NumberOfItemsToIndex(
|
||||
out int plIncrementalCount,
|
||||
out int plNotificationQueue,
|
||||
out int plHighPriorityQueue);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
string URLBeingIndexed();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
uint GetURLIndexingState([MarshalAs(UnmanagedType.LPWStr), In] string psz);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.Interface)]
|
||||
object GetPersistentItemsChangedSink();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void RegisterViewForNotification(
|
||||
[MarshalAs(UnmanagedType.LPWStr), In] string pszView,
|
||||
[MarshalAs(UnmanagedType.Interface), In] object pViewChangedSink,
|
||||
out uint pdwCookie);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetItemsChangedSink(
|
||||
[MarshalAs(UnmanagedType.Interface), In] object pISearchNotifyInlineSite,
|
||||
[In] ref Guid riid,
|
||||
out IntPtr ppv,
|
||||
out Guid pGUIDCatalogResetSignature,
|
||||
out Guid pGUIDCheckPointSignature,
|
||||
out uint pdwLastCheckPointNumber);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void UnregisterViewForNotification([In] uint dwCookie);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void SetExtensionClusion([MarshalAs(UnmanagedType.LPWStr), In] string pszExtension, [In] int fExclude);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.Interface)]
|
||||
object EnumerateExcludedExtensions();
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.Interface)]
|
||||
CSearchQueryHelper GetQueryHelper();
|
||||
|
||||
[DispId(1610678295)]
|
||||
int DiacriticSensitivity
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.Interface)]
|
||||
object GetCrawlScopeManager();
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch;
|
||||
|
||||
[ComConversionLoss]
|
||||
[Guid("AB310581-AC80-11D1-8DF3-00C04FB6EF69")]
|
||||
[InterfaceType(1)]
|
||||
[ComImport]
|
||||
public interface ISearchManager
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetIndexerVersionStr([MarshalAs(UnmanagedType.LPWStr)] out string ppszVersionString);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetIndexerVersion(out uint pdwMajor, out uint pdwMinor);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
IntPtr GetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void SetParameter([MarshalAs(UnmanagedType.LPWStr), In] string pszName, [In] ref object pValue);
|
||||
|
||||
[DispId(1610678276)]
|
||||
string ProxyName
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678277)]
|
||||
string BypassList
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void SetProxy(
|
||||
[In] object sUseProxy,
|
||||
[In] int fLocalByPassProxy,
|
||||
[In] uint dwPortNumber,
|
||||
[MarshalAs(UnmanagedType.LPWStr), In] string pszProxyName,
|
||||
[MarshalAs(UnmanagedType.LPWStr), In] string pszByPassList);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.Interface)]
|
||||
CSearchCatalogManager GetCatalog([MarshalAs(UnmanagedType.LPWStr), In] string pszCatalog);
|
||||
|
||||
[DispId(1610678280)]
|
||||
string UserAgent
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: MarshalAs(UnmanagedType.LPWStr)]
|
||||
[param: In]
|
||||
set;
|
||||
}
|
||||
|
||||
[DispId(1610678282)]
|
||||
object UseProxy
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678283)]
|
||||
int LocalBypass
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678284)]
|
||||
uint PortNumber
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Indexer.SystemSearch;
|
||||
|
||||
[Guid("AB310581-AC80-11D1-8DF3-00C04FB6EF63")]
|
||||
[InterfaceType(1)]
|
||||
[ComImport]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:Property accessors should follow order", Justification = "The order of the property accessors must match the order in which the methods were defined in the vtable")]
|
||||
public interface ISearchQueryHelper
|
||||
{
|
||||
[DispId(1610678272)]
|
||||
string ConnectionString
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678273)]
|
||||
uint QueryContentLocale
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678275)]
|
||||
uint QueryKeywordLocale
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678277)]
|
||||
object QueryTermExpansion
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678279)]
|
||||
object QuerySyntax
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678281)]
|
||||
string QueryContentProperties
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: MarshalAs(UnmanagedType.LPWStr)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678283)]
|
||||
string QuerySelectColumns
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: MarshalAs(UnmanagedType.LPWStr)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678285)]
|
||||
string QueryWhereRestrictions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: MarshalAs(UnmanagedType.LPWStr)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[DispId(1610678287)]
|
||||
string QuerySorting
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: MarshalAs(UnmanagedType.LPWStr)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
get;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[return: MarshalAs(UnmanagedType.LPWStr)]
|
||||
string GenerateSQLFromUserQuery([MarshalAs(UnmanagedType.LPWStr), In] string pszQuery);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void WriteProperties(
|
||||
[In] int itemID,
|
||||
[In] uint dwNumberOfColumns,
|
||||
[In] ref object pColumns,
|
||||
[In] ref object pValues,
|
||||
[In] ref object pftGatherModifiedTime);
|
||||
|
||||
[DispId(1610678291)]
|
||||
int QueryMaxResults
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
[param: In]
|
||||
set;
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
get;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer;
|
||||
|
||||
public partial class IndexerCommandsProvider : CommandProvider
|
||||
{
|
||||
private readonly FallbackOpenFileItem _fallbackFileItem = new();
|
||||
|
||||
public IndexerCommandsProvider()
|
||||
{
|
||||
Id = "Files";
|
||||
DisplayName = Resources.IndexerCommandsProvider_DisplayName;
|
||||
Icon = Icons.FileExplorer;
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands()
|
||||
{
|
||||
return [
|
||||
new CommandItem(new IndexerPage())
|
||||
{
|
||||
Title = Resources.Indexer_Title,
|
||||
Subtitle = Resources.Indexer_Subtitle,
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
public override IFallbackCommandItem[] FallbackCommands() =>
|
||||
[
|
||||
_fallbackFileItem
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
DBID
|
||||
SHOW_WINDOW_CMD
|
||||
CoCreateInstance
|
||||
GetErrorInfo
|
||||
ICommandText
|
||||
IDBCreateCommand
|
||||
IDBCreateSession
|
||||
IDBInitialize
|
||||
IGetRow
|
||||
IPropertyStore
|
||||
ShellExecuteEx
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Data;
|
||||
using Microsoft.CmdPal.Ext.Indexer.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
#nullable enable
|
||||
namespace Microsoft.CmdPal.Ext.Indexer;
|
||||
|
||||
public sealed partial class DirectoryPage : ListPage
|
||||
{
|
||||
private readonly string _path;
|
||||
|
||||
private List<IndexerListItem>? _directoryContents;
|
||||
|
||||
public DirectoryPage(string path)
|
||||
{
|
||||
_path = path;
|
||||
Icon = Icons.FileExplorer;
|
||||
Name = Resources.Indexer_Command_Browse;
|
||||
Title = path;
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
if (_directoryContents != null)
|
||||
{
|
||||
return _directoryContents.ToArray();
|
||||
}
|
||||
|
||||
if (!Path.Exists(_path))
|
||||
{
|
||||
EmptyContent = new CommandItem(
|
||||
title: Resources.Indexer_File_Does_Not_Exist,
|
||||
subtitle: $"{_path}");
|
||||
return [];
|
||||
}
|
||||
|
||||
var attr = File.GetAttributes(_path);
|
||||
|
||||
// detect whether its a directory or file
|
||||
if ((attr & FileAttributes.Directory) != FileAttributes.Directory)
|
||||
{
|
||||
EmptyContent = new CommandItem(
|
||||
title: Resources.Indexer_File_Is_File_Not_Folder, subtitle: $"{_path}")
|
||||
{
|
||||
Icon = Icons.Document,
|
||||
};
|
||||
return [];
|
||||
}
|
||||
|
||||
var contents = Directory.EnumerateFileSystemEntries(_path);
|
||||
|
||||
if (!contents.Any())
|
||||
{
|
||||
var item = new IndexerItem() { FullPath = _path, FileName = Path.GetFileName(_path) };
|
||||
var listItemForUs = new IndexerListItem(item, IncludeBrowseCommand.Exclude);
|
||||
EmptyContent = new CommandItem(
|
||||
title: Resources.Indexer_Folder_Is_Empty, subtitle: $"{_path}")
|
||||
{
|
||||
Icon = Icons.FolderOpen,
|
||||
Command = listItemForUs.Command,
|
||||
MoreCommands = listItemForUs.MoreCommands,
|
||||
};
|
||||
return [];
|
||||
}
|
||||
|
||||
_directoryContents = contents
|
||||
.Select(s => new IndexerItem() { FullPath = s, FileName = Path.GetFileName(s) })
|
||||
.Select(i => new IndexerListItem(i, IncludeBrowseCommand.AsDefault))
|
||||
.ToList();
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
foreach (var item in _directoryContents)
|
||||
{
|
||||
IconInfo? icon = null;
|
||||
try
|
||||
{
|
||||
var stream = ThumbnailHelper.GetThumbnail(item.FilePath).Result;
|
||||
if (stream != null)
|
||||
{
|
||||
var data = new IconData(RandomAccessStreamReference.CreateFromStream(stream));
|
||||
icon = new IconInfo(data, data);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
item.Icon = icon;
|
||||
}
|
||||
});
|
||||
|
||||
return _directoryContents.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer;
|
||||
|
||||
internal sealed class Icons
|
||||
{
|
||||
internal static IconInfo FileExplorerSegoe { get; } = new("\uEC50");
|
||||
|
||||
internal static IconInfo FileExplorer { get; } = IconHelpers.FromRelativePath("Assets\\FileExplorer.png");
|
||||
|
||||
internal static IconInfo OpenFile { get; } = new("\uE8E5"); // OpenFile
|
||||
|
||||
internal static IconInfo Document { get; } = new("\uE8A5"); // Document
|
||||
|
||||
internal static IconInfo FolderOpen { get; } = new("\uE838"); // FolderOpen
|
||||
}
|
||||
198
src/modules/cmdpal/exts/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs
generated
Normal file
198
src/modules/cmdpal/exts/Microsoft.CmdPal.Ext.Indexer/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,198 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Indexer.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Ext.Indexer.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Browse.
|
||||
/// </summary>
|
||||
internal static string Indexer_Command_Browse {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Command_Browse", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Copy path.
|
||||
/// </summary>
|
||||
internal static string Indexer_Command_CopyPath {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Command_CopyPath", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open.
|
||||
/// </summary>
|
||||
internal static string Indexer_Command_OpenFile {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Command_OpenFile", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open path in console.
|
||||
/// </summary>
|
||||
internal static string Indexer_Command_OpenPathInConsole {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Command_OpenPathInConsole", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Properties.
|
||||
/// </summary>
|
||||
internal static string Indexer_Command_OpenProperties {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Command_OpenProperties", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open with.
|
||||
/// </summary>
|
||||
internal static string Indexer_Command_OpenWith {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Command_OpenWith", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Show in folder.
|
||||
/// </summary>
|
||||
internal static string Indexer_Command_ShowInFolder {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Command_ShowInFolder", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This file doesn't exist.
|
||||
/// </summary>
|
||||
internal static string Indexer_File_Does_Not_Exist {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_File_Does_Not_Exist", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This is a file, not a folder.
|
||||
/// </summary>
|
||||
internal static string Indexer_File_Is_File_Not_Folder {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_File_Is_File_Not_Folder", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Find file from path.
|
||||
/// </summary>
|
||||
internal static string Indexer_Find_Path_fallback_display_title {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Find_Path_fallback_display_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to This folder is empty.
|
||||
/// </summary>
|
||||
internal static string Indexer_Folder_Is_Empty {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Folder_Is_Empty", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search for files and folders....
|
||||
/// </summary>
|
||||
internal static string Indexer_PlaceholderText {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_PlaceholderText", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search files on this device.
|
||||
/// </summary>
|
||||
internal static string Indexer_Subtitle {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Subtitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search files.
|
||||
/// </summary>
|
||||
internal static string Indexer_Title {
|
||||
get {
|
||||
return ResourceManager.GetString("Indexer_Title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to File search.
|
||||
/// </summary>
|
||||
internal static string IndexerCommandsProvider_DisplayName {
|
||||
get {
|
||||
return ResourceManager.GetString("IndexerCommandsProvider_DisplayName", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="IndexerCommandsProvider_DisplayName" xml:space="preserve">
|
||||
<value>File search</value>
|
||||
</data>
|
||||
<data name="Indexer_Command_Browse" xml:space="preserve">
|
||||
<value>Browse</value>
|
||||
</data>
|
||||
<data name="Indexer_Command_CopyPath" xml:space="preserve">
|
||||
<value>Copy path</value>
|
||||
</data>
|
||||
<data name="Indexer_Command_OpenFile" xml:space="preserve">
|
||||
<value>Open</value>
|
||||
</data>
|
||||
<data name="Indexer_Command_OpenPathInConsole" xml:space="preserve">
|
||||
<value>Open path in console</value>
|
||||
</data>
|
||||
<data name="Indexer_Command_OpenProperties" xml:space="preserve">
|
||||
<value>Properties</value>
|
||||
</data>
|
||||
<data name="Indexer_Command_OpenWith" xml:space="preserve">
|
||||
<value>Open with</value>
|
||||
</data>
|
||||
<data name="Indexer_Command_ShowInFolder" xml:space="preserve">
|
||||
<value>Show in folder</value>
|
||||
</data>
|
||||
<data name="Indexer_File_Does_Not_Exist" xml:space="preserve">
|
||||
<value>This file doesn't exist</value>
|
||||
</data>
|
||||
<data name="Indexer_File_Is_File_Not_Folder" xml:space="preserve">
|
||||
<value>This is a file, not a folder</value>
|
||||
</data>
|
||||
<data name="Indexer_Find_Path_fallback_display_title" xml:space="preserve">
|
||||
<value>Find file from path</value>
|
||||
</data>
|
||||
<data name="Indexer_Folder_Is_Empty" xml:space="preserve">
|
||||
<value>This folder is empty</value>
|
||||
</data>
|
||||
<data name="Indexer_PlaceholderText" xml:space="preserve">
|
||||
<value>Search for files and folders...</value>
|
||||
</data>
|
||||
<data name="Indexer_Subtitle" xml:space="preserve">
|
||||
<value>Search files on this device</value>
|
||||
</data>
|
||||
<data name="Indexer_Title" xml:space="preserve">
|
||||
<value>Search files</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,19 @@
|
||||
<svg width="260" height="260" viewBox="0 0 260 260" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.7189 166.405L34.8123 80.9387C37.8287 65.8564 51.0714 55 66.4524 55H210.641C231.003 55 246.274 73.6283 242.281 93.5947L225.188 179.061C222.171 194.144 208.929 205 193.548 205H49.359C28.9972 205 13.7257 186.372 17.7189 166.405Z" fill="url(#paint0_linear_15_159)" stroke="url(#paint1_linear_15_159)" stroke-width="32.2667"/>
|
||||
<path d="M66.3623 129.973L70.5337 110.506C73.7217 95.6287 86.8692 85 102.084 85H162.086C182.628 85 197.94 103.941 193.636 124.027L189.465 143.494C186.277 158.371 173.129 169 157.914 169H97.9127C77.3703 169 62.0581 150.059 66.3623 129.973Z" fill="url(#paint2_linear_15_159)"/>
|
||||
<path d="M85.1317 114.211L85.7539 110.478C87.0504 102.699 93.7811 96.9971 101.668 96.9971H164.956C174.925 96.9971 182.509 105.949 180.87 115.783L180.248 119.516C178.951 127.295 172.22 132.997 164.334 132.997H101.045C91.0761 132.997 83.4927 124.045 85.1317 114.211Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_15_159" x1="148" y1="61" x2="130" y2="205" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#CDEDFC"/>
|
||||
<stop offset="1" stop-color="#BFE3F9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_15_159" x1="46" y1="49" x2="268" y2="253" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#3BCAF4"/>
|
||||
<stop offset="1" stop-color="#006AAA"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_15_159" x1="93.9993" y1="73" x2="216.411" y2="214.919" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#5ED0F2"/>
|
||||
<stop offset="1" stop-color="#1494DF"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Ext.Shell.Commands;
|
||||
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||
using Microsoft.CmdPal.Ext.Shell.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Shell;
|
||||
|
||||
internal sealed partial class FallbackExecuteItem : FallbackCommandItem
|
||||
{
|
||||
private readonly ExecuteItem _executeItem;
|
||||
|
||||
public FallbackExecuteItem(SettingsManager settings)
|
||||
: base(new ExecuteItem(string.Empty, settings), Resources.shell_command_display_title)
|
||||
{
|
||||
_executeItem = (ExecuteItem)this.Command!;
|
||||
Title = string.Empty;
|
||||
_executeItem.Name = string.Empty;
|
||||
Subtitle = Properties.Resources.generic_run_command;
|
||||
Icon = new IconInfo("\uE756");
|
||||
}
|
||||
|
||||
public override void UpdateQuery(string query)
|
||||
{
|
||||
_executeItem.Cmd = query;
|
||||
_executeItem.Name = string.IsNullOrEmpty(query) ? string.Empty : Properties.Resources.generic_run_command;
|
||||
Title = query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||
|
||||
public enum ExecutionShell
|
||||
{
|
||||
Cmd = 0,
|
||||
Powershell = 1,
|
||||
RunCommand = 2,
|
||||
WindowsTerminalPowerShell = 3,
|
||||
WindowsTerminalPowerShellSeven = 4,
|
||||
WindowsTerminalCmd = 5,
|
||||
PowerShellSeven = 6,
|
||||
}
|
||||
12
src/modules/cmdpal/exts/Microsoft.CmdPal.Ext.Shell/Icons.cs
Normal file
12
src/modules/cmdpal/exts/Microsoft.CmdPal.Ext.Shell/Icons.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Shell;
|
||||
|
||||
internal sealed class Icons
|
||||
{
|
||||
internal static IconInfo RunV2 { get; } = IconHelpers.FromRelativePath("Assets\\Run@2x.svg");
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
using BrowserInfo = Microsoft.CmdPal.Ext.WebSearch.Helpers.DefaultBrowserInfo;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch.Commands;
|
||||
|
||||
internal sealed partial class SearchWebCommand : InvokableCommand
|
||||
{
|
||||
private readonly SettingsManager _settingsManager;
|
||||
|
||||
public string Arguments { get; internal set; } = string.Empty;
|
||||
|
||||
internal SearchWebCommand(string arguments, SettingsManager settingsManager)
|
||||
{
|
||||
Arguments = arguments;
|
||||
BrowserInfo.UpdateIfTimePassed();
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\WebSearch.png");
|
||||
Name = Properties.Resources.open_in_default_browser;
|
||||
_settingsManager = settingsManager;
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
if (!ShellHelpers.OpenCommandInShell(BrowserInfo.Path, BrowserInfo.ArgumentsPattern, $"? {Arguments}"))
|
||||
{
|
||||
// TODO GH# 138 --> actually display feedback from the extension somewhere.
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
if (_settingsManager.ShowHistory != Resources.history_none)
|
||||
{
|
||||
_settingsManager.SaveHistory(new HistoryItem(Arguments, DateTime.Now));
|
||||
}
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
using Microsoft.CmdPal.Ext.WebSearch.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using BrowserInfo = Microsoft.CmdPal.Ext.WebSearch.Helpers.DefaultBrowserInfo;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch.Commands;
|
||||
|
||||
internal sealed partial class FallbackExecuteSearchItem : FallbackCommandItem
|
||||
{
|
||||
private readonly SearchWebCommand _executeItem;
|
||||
private static readonly CompositeFormat PluginOpen = System.Text.CompositeFormat.Parse(Properties.Resources.plugin_open);
|
||||
|
||||
public FallbackExecuteSearchItem(SettingsManager settings)
|
||||
: base(new SearchWebCommand(string.Empty, settings), Resources.command_item_title)
|
||||
{
|
||||
_executeItem = (SearchWebCommand)this.Command!;
|
||||
Title = string.Empty;
|
||||
_executeItem.Name = string.Empty;
|
||||
Subtitle = string.Format(CultureInfo.CurrentCulture, PluginOpen, BrowserInfo.Name ?? BrowserInfo.MSEdgeName);
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\WebSearch.png");
|
||||
}
|
||||
|
||||
public override void UpdateQuery(string query)
|
||||
{
|
||||
_executeItem.Arguments = query;
|
||||
_executeItem.Name = string.IsNullOrEmpty(query) ? string.Empty : Properties.Resources.open_in_default_browser;
|
||||
Title = query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WebSearch.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Contains information (e.g. path to executable, name...) about the default browser.
|
||||
/// </summary>
|
||||
public static class DefaultBrowserInfo
|
||||
{
|
||||
private static readonly Lock _updateLock = new();
|
||||
|
||||
/// <summary>Gets the path to the MS Edge browser executable.</summary>
|
||||
public static string MSEdgePath => System.IO.Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),
|
||||
@"Microsoft\Edge\Application\msedge.exe");
|
||||
|
||||
/// <summary>Gets the command line pattern of the MS Edge.</summary>
|
||||
public const string MSEdgeArgumentsPattern = "--single-argument %1";
|
||||
|
||||
public const string MSEdgeName = "Microsoft Edge";
|
||||
|
||||
/// <summary>Gets the path to default browser's executable.</summary>
|
||||
public static string? Path { get; private set; }
|
||||
|
||||
/// <summary>Gets <see cref="Path"/> since the icon is embedded in the executable.</summary>
|
||||
public static string? IconPath => Path;
|
||||
|
||||
/// <summary>Gets the user-friendly name of the default browser.</summary>
|
||||
public static string? Name { get; private set; }
|
||||
|
||||
/// <summary>Gets the command line pattern of the default browser.</summary>
|
||||
public static string? ArgumentsPattern { get; private set; }
|
||||
|
||||
public static bool IsDefaultBrowserSet => !string.IsNullOrEmpty(Path);
|
||||
|
||||
public const long UpdateTimeout = 300;
|
||||
|
||||
private static long _lastUpdateTickCount = -UpdateTimeout;
|
||||
|
||||
private static bool _updatedOnce;
|
||||
private static bool _errorLogged;
|
||||
|
||||
/// <summary>
|
||||
/// Updates only if at least more than 300ms has passed since the last update, to avoid multiple calls to <see cref="Update"/>.
|
||||
/// (because of multiple plugins calling update at the same time.)
|
||||
/// </summary>
|
||||
public static void UpdateIfTimePassed()
|
||||
{
|
||||
var curTickCount = Environment.TickCount64;
|
||||
if (curTickCount - _lastUpdateTickCount >= UpdateTimeout)
|
||||
{
|
||||
_lastUpdateTickCount = curTickCount;
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Consider using <see cref="UpdateIfTimePassed"/> to avoid updating multiple times.
|
||||
/// (because of multiple plugins calling update at the same time.)
|
||||
/// </summary>
|
||||
public static void Update()
|
||||
{
|
||||
lock (_updateLock)
|
||||
{
|
||||
if (!_updatedOnce)
|
||||
{
|
||||
// Log.Info("I've tried updating the chosen Web Browser info at least once.", typeof(DefaultBrowserInfo));
|
||||
_updatedOnce = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var progId = GetRegistryValue(
|
||||
@"HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice",
|
||||
"ProgId");
|
||||
var appName = GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}\Application", "ApplicationName")
|
||||
?? GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}", "FriendlyTypeName");
|
||||
|
||||
if (appName != null)
|
||||
{
|
||||
// Handle indirect strings:
|
||||
if (appName.StartsWith('@'))
|
||||
{
|
||||
appName = GetIndirectString(appName);
|
||||
}
|
||||
|
||||
appName = appName
|
||||
.Replace("URL", null, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("HTML", null, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("Document", null, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("Web", null, StringComparison.OrdinalIgnoreCase)
|
||||
.TrimEnd();
|
||||
}
|
||||
|
||||
Name = appName;
|
||||
|
||||
var commandPattern = GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}\shell\open\command", null);
|
||||
|
||||
if (string.IsNullOrEmpty(commandPattern))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(commandPattern),
|
||||
"Default browser program command is not specified.");
|
||||
}
|
||||
|
||||
if (commandPattern.StartsWith('@'))
|
||||
{
|
||||
commandPattern = GetIndirectString(commandPattern);
|
||||
}
|
||||
|
||||
// HACK: for firefox installed through Microsoft store
|
||||
// When installed through Microsoft Firefox the commandPattern does not have
|
||||
// quotes for the path. As the Program Files does have a space
|
||||
// the extracted path would be invalid, here we add the quotes to fix it
|
||||
const string FirefoxExecutableName = "firefox.exe";
|
||||
if (commandPattern.Contains(FirefoxExecutableName) && commandPattern.Contains(@"\WindowsApps\") && (!commandPattern.StartsWith('\"')))
|
||||
{
|
||||
var pathEndIndex = commandPattern.IndexOf(FirefoxExecutableName, StringComparison.Ordinal) + FirefoxExecutableName.Length;
|
||||
commandPattern = commandPattern.Insert(pathEndIndex, "\"");
|
||||
commandPattern = commandPattern.Insert(0, "\"");
|
||||
}
|
||||
|
||||
if (commandPattern.StartsWith('\"'))
|
||||
{
|
||||
var endQuoteIndex = commandPattern.IndexOf('\"', 1);
|
||||
if (endQuoteIndex != -1)
|
||||
{
|
||||
Path = commandPattern.Substring(1, endQuoteIndex - 1);
|
||||
ArgumentsPattern = commandPattern.Substring(endQuoteIndex + 1).Trim();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var spaceIndex = commandPattern.IndexOf(' ');
|
||||
if (spaceIndex != -1)
|
||||
{
|
||||
Path = commandPattern.Substring(0, spaceIndex);
|
||||
ArgumentsPattern = commandPattern.Substring(spaceIndex + 1).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
// Packaged applications could be an URI. Example: shell:AppsFolder\Microsoft.MicrosoftEdge.Stable_8wekyb3d8bbwe!App
|
||||
if (!System.IO.Path.Exists(Path) && !Uri.TryCreate(Path, UriKind.Absolute, out _))
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Command validation failed: {commandPattern}",
|
||||
nameof(commandPattern));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Path))
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(Path),
|
||||
"Default browser program path could not be determined.");
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Fallback to MS Edge
|
||||
Path = MSEdgePath;
|
||||
Name = MSEdgeName;
|
||||
ArgumentsPattern = MSEdgeArgumentsPattern;
|
||||
|
||||
if (!_errorLogged)
|
||||
{
|
||||
// Log.Exception("Exception when retrieving browser path/name. Path and Name are set to use Microsoft Edge.", e, typeof(DefaultBrowserInfo));
|
||||
_errorLogged = true;
|
||||
}
|
||||
}
|
||||
|
||||
string? GetRegistryValue(string registryLocation, string? valueName)
|
||||
{
|
||||
return Microsoft.Win32.Registry.GetValue(registryLocation, valueName, null) as string;
|
||||
}
|
||||
|
||||
string GetIndirectString(string str)
|
||||
{
|
||||
var stringBuilder = new StringBuilder(128);
|
||||
unsafe
|
||||
{
|
||||
var buffer = stackalloc char[128];
|
||||
var capacity = 128;
|
||||
void* reserved = null;
|
||||
|
||||
// S_OK == 0
|
||||
if (global::Windows.Win32.PInvoke.SHLoadIndirectString(
|
||||
str,
|
||||
buffer,
|
||||
(uint)capacity,
|
||||
ref reserved)
|
||||
== 0)
|
||||
{
|
||||
return new string(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
throw new ArgumentNullException(nameof(str), "Could not load indirect string.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
SHLoadIndirectString
|
||||
@@ -0,0 +1,103 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.WinGet</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||
<UseWinUI>false</UseWinUI>
|
||||
<Nullable>enable</Nullable>
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<CsWinRTIncludes>Microsoft.Management.Deployment</CsWinRTIncludes>
|
||||
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Store.dark.png" />
|
||||
<None Remove="Assets\Store.dark.svg" />
|
||||
<None Remove="Assets\Store.light.png" />
|
||||
<None Remove="Assets\Store.light.svg" />
|
||||
<None Remove="Assets\WinGet.png" />
|
||||
<None Remove="Assets\WinGet.svg" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
||||
<ProjectReference Include="..\..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||
package has not yet been restored.
|
||||
-->
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<CsWinRTInputs Include="$(PkgMicrosoft_WindowsPackageManager_ComInterop)\lib\uap10.0\Microsoft.Management.Deployment.winmd" />
|
||||
|
||||
<!-- Before v1.10 of this package, it wasn't in the uap10.0 dir -->
|
||||
<Content Include="$(PkgMicrosoft_WindowsPackageManager_ComInterop)\lib\uap10.0\Microsoft.Management.Deployment.winmd" Link="Microsoft.Management.Deployment.winmd" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.WindowsPackageManager.ComInterop">
|
||||
<NoWarn>NU1701</NoWarn>
|
||||
<GeneratePathProperty>true</GeneratePathProperty>
|
||||
<IncludeAssets>none</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
||||
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
||||
the Windows App SDK Nuget package has not yet been restored.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="Assets\Store.dark.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Store.dark.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Store.light.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Store.light.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,6 @@
|
||||
CoCreateInstance
|
||||
CoMarshalInterface
|
||||
CoUnmarshalInterface
|
||||
CreateStreamOnHGlobal
|
||||
MSHCTX
|
||||
MSHLFLAGS
|
||||
@@ -0,0 +1,226 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.Management.Deployment;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WinGet.Pages;
|
||||
|
||||
public partial class InstallPackageCommand : InvokableCommand
|
||||
{
|
||||
private readonly CatalogPackage _package;
|
||||
|
||||
private readonly StatusMessage _installBanner = new();
|
||||
private IAsyncOperationWithProgress<InstallResult, InstallProgress>? _installAction;
|
||||
private IAsyncOperationWithProgress<UninstallResult, UninstallProgress>? _unInstallAction;
|
||||
private Task? _installTask;
|
||||
|
||||
public bool IsInstalled { get; private set; }
|
||||
|
||||
public static IconInfo CompletedIcon { get; } = new("\uE930"); // Completed
|
||||
|
||||
public static IconInfo DownloadIcon { get; } = new("\uE896"); // Download
|
||||
|
||||
public static IconInfo DeleteIcon { get; } = new("\uE74D"); // Delete
|
||||
|
||||
public event EventHandler<InstallPackageCommand>? InstallStateChanged;
|
||||
|
||||
private static readonly CompositeFormat UninstallingPackage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_uninstalling_package);
|
||||
private static readonly CompositeFormat InstallingPackage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_installing_package);
|
||||
private static readonly CompositeFormat InstallPackageFinished = System.Text.CompositeFormat.Parse(Properties.Resources.winget_install_package_finished);
|
||||
private static readonly CompositeFormat UninstallPackageFinished = System.Text.CompositeFormat.Parse(Properties.Resources.winget_uninstall_package_finished);
|
||||
private static readonly CompositeFormat QueuedPackageDownload = System.Text.CompositeFormat.Parse(Properties.Resources.winget_queued_package_download);
|
||||
private static readonly CompositeFormat InstallPackageFinishing = System.Text.CompositeFormat.Parse(Properties.Resources.winget_install_package_finishing);
|
||||
private static readonly CompositeFormat QueuedPackageUninstall = System.Text.CompositeFormat.Parse(Properties.Resources.winget_queued_package_uninstall);
|
||||
private static readonly CompositeFormat UninstallPackageFinishing = System.Text.CompositeFormat.Parse(Properties.Resources.winget_uninstall_package_finishing);
|
||||
private static readonly CompositeFormat DownloadProgress = System.Text.CompositeFormat.Parse(Properties.Resources.winget_download_progress);
|
||||
|
||||
public InstallPackageCommand(CatalogPackage package, bool isInstalled)
|
||||
{
|
||||
_package = package;
|
||||
IsInstalled = isInstalled;
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
internal void FakeChangeStatus()
|
||||
{
|
||||
IsInstalled = !IsInstalled;
|
||||
UpdateAppearance();
|
||||
}
|
||||
|
||||
private void UpdateAppearance()
|
||||
{
|
||||
Icon = IsInstalled ? CompletedIcon : DownloadIcon;
|
||||
Name = IsInstalled ? Properties.Resources.winget_uninstall_name : Properties.Resources.winget_install_name;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
// TODO: LOCK in here, so this can only be invoked once until the
|
||||
// install / uninstall is done. Just use like, an atomic
|
||||
if (_installTask != null)
|
||||
{
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
if (IsInstalled)
|
||||
{
|
||||
// Uninstall
|
||||
_installBanner.State = MessageState.Info;
|
||||
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, UninstallingPackage, _package.Name);
|
||||
WinGetExtensionHost.Instance.ShowStatus(_installBanner, StatusContext.Extension);
|
||||
|
||||
var installOptions = WinGetStatics.WinGetFactory.CreateUninstallOptions();
|
||||
installOptions.PackageUninstallScope = PackageUninstallScope.Any;
|
||||
_unInstallAction = WinGetStatics.Manager.UninstallPackageAsync(_package, installOptions);
|
||||
|
||||
var handler = new AsyncOperationProgressHandler<UninstallResult, UninstallProgress>(OnUninstallProgress);
|
||||
_unInstallAction.Progress = handler;
|
||||
|
||||
_installTask = Task.Run(() => TryDoInstallOperation(_unInstallAction));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Install
|
||||
_installBanner.State = MessageState.Info;
|
||||
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallingPackage, _package.Name);
|
||||
WinGetExtensionHost.Instance.ShowStatus(_installBanner, StatusContext.Extension);
|
||||
|
||||
var installOptions = WinGetStatics.WinGetFactory.CreateInstallOptions();
|
||||
installOptions.PackageInstallScope = PackageInstallScope.Any;
|
||||
_installAction = WinGetStatics.Manager.InstallPackageAsync(_package, installOptions);
|
||||
|
||||
var handler = new AsyncOperationProgressHandler<InstallResult, InstallProgress>(OnInstallProgress);
|
||||
_installAction.Progress = handler;
|
||||
|
||||
_installTask = Task.Run(() => TryDoInstallOperation(_installAction));
|
||||
}
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
private async void TryDoInstallOperation<T_Operation, T_Progress>(
|
||||
IAsyncOperationWithProgress<T_Operation, T_Progress> action)
|
||||
{
|
||||
try
|
||||
{
|
||||
await action.AsTask();
|
||||
_installBanner.Message = IsInstalled ?
|
||||
string.Format(CultureInfo.CurrentCulture, UninstallPackageFinished, _package.Name) :
|
||||
string.Format(CultureInfo.CurrentCulture, InstallPackageFinished, _package.Name);
|
||||
|
||||
_installBanner.Progress = null;
|
||||
_installBanner.State = MessageState.Success;
|
||||
_installTask = null;
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
Thread.Sleep(2500);
|
||||
if (_installTask == null)
|
||||
{
|
||||
WinGetExtensionHost.Instance.HideStatus(_installBanner);
|
||||
}
|
||||
});
|
||||
InstallStateChanged?.Invoke(this, this);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_installBanner.State = MessageState.Error;
|
||||
_installBanner.Progress = null;
|
||||
_installBanner.Message = ex.Message;
|
||||
_installTask = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatBytes(ulong bytes)
|
||||
{
|
||||
const long KB = 1024;
|
||||
const long MB = KB * 1024;
|
||||
const long GB = MB * 1024;
|
||||
|
||||
return bytes >= GB
|
||||
? $"{bytes / (double)GB:F2} GB"
|
||||
: bytes >= MB ?
|
||||
$"{bytes / (double)MB:F2} MB"
|
||||
: bytes >= KB
|
||||
? $"{bytes / (double)KB:F2} KB"
|
||||
: $"{bytes} bytes";
|
||||
}
|
||||
|
||||
private void OnInstallProgress(
|
||||
IAsyncOperationWithProgress<InstallResult, InstallProgress> operation,
|
||||
InstallProgress progress)
|
||||
{
|
||||
switch (progress.State)
|
||||
{
|
||||
case PackageInstallProgressState.Queued:
|
||||
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, QueuedPackageDownload, _package.Name);
|
||||
break;
|
||||
case PackageInstallProgressState.Downloading:
|
||||
if (progress.BytesRequired > 0)
|
||||
{
|
||||
var downloadText = string.Format(CultureInfo.CurrentCulture, DownloadProgress, FormatBytes(progress.BytesDownloaded), FormatBytes(progress.BytesRequired));
|
||||
_installBanner.Progress ??= new ProgressState() { IsIndeterminate = false };
|
||||
var downloaded = progress.BytesDownloaded / (float)progress.BytesRequired;
|
||||
var percent = downloaded * 100.0f;
|
||||
((ProgressState)_installBanner.Progress).ProgressPercent = (uint)percent;
|
||||
_installBanner.Message = downloadText;
|
||||
}
|
||||
|
||||
break;
|
||||
case PackageInstallProgressState.Installing:
|
||||
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallingPackage, _package.Name);
|
||||
_installBanner.Progress = new ProgressState() { IsIndeterminate = true };
|
||||
break;
|
||||
case PackageInstallProgressState.PostInstall:
|
||||
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallPackageFinishing, _package.Name);
|
||||
break;
|
||||
case PackageInstallProgressState.Finished:
|
||||
_installBanner.Message = Properties.Resources.winget_install_finished;
|
||||
|
||||
// progressBar.IsIndeterminate(false);
|
||||
_installBanner.Progress = null;
|
||||
_installBanner.State = MessageState.Success;
|
||||
break;
|
||||
default:
|
||||
_installBanner.Message = string.Empty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUninstallProgress(
|
||||
IAsyncOperationWithProgress<UninstallResult, UninstallProgress> operation,
|
||||
UninstallProgress progress)
|
||||
{
|
||||
switch (progress.State)
|
||||
{
|
||||
case PackageUninstallProgressState.Queued:
|
||||
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, QueuedPackageUninstall, _package.Name);
|
||||
break;
|
||||
|
||||
case PackageUninstallProgressState.Uninstalling:
|
||||
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, UninstallingPackage, _package.Name);
|
||||
_installBanner.Progress = new ProgressState() { IsIndeterminate = true };
|
||||
break;
|
||||
case PackageUninstallProgressState.PostUninstall:
|
||||
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, UninstallPackageFinishing, _package.Name);
|
||||
break;
|
||||
case PackageUninstallProgressState.Finished:
|
||||
_installBanner.Message = Properties.Resources.winget_uninstall_finished;
|
||||
_installBanner.Progress = null;
|
||||
_installBanner.State = MessageState.Success;
|
||||
break;
|
||||
default:
|
||||
_installBanner.Message = string.Empty;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.Management.Deployment;
|
||||
using Windows.Foundation.Metadata;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WinGet.Pages;
|
||||
|
||||
public partial class InstallPackageListItem : ListItem
|
||||
{
|
||||
private readonly CatalogPackage _package;
|
||||
|
||||
// Lazy-init the details
|
||||
private readonly Lazy<Details?> _details;
|
||||
|
||||
public override IDetails? Details { get => _details.Value; set => base.Details = value; }
|
||||
|
||||
private InstallPackageCommand? _installCommand;
|
||||
|
||||
public InstallPackageListItem(CatalogPackage package)
|
||||
: base(new NoOpCommand())
|
||||
{
|
||||
_package = package;
|
||||
|
||||
PackageVersionInfo version = _package.DefaultInstallVersion;
|
||||
string versionText = version.Version;
|
||||
string versionTagText = versionText == "Unknown" && version.PackageCatalog.Info.Id == "StoreEdgeFD" ? "msstore" : versionText;
|
||||
|
||||
Title = _package.Name;
|
||||
Subtitle = _package.Id;
|
||||
Tags = [new Tag() { Text = versionTagText }];
|
||||
|
||||
_details = new Lazy<Details?>(() => BuildDetails(version));
|
||||
|
||||
_ = Task.Run(UpdatedInstalledStatus);
|
||||
}
|
||||
|
||||
private Details? BuildDetails(PackageVersionInfo? version)
|
||||
{
|
||||
CatalogPackageMetadata? metadata = version?.GetCatalogPackageMetadata();
|
||||
if (metadata != null)
|
||||
{
|
||||
string description = string.IsNullOrEmpty(metadata.Description) ? metadata.ShortDescription : metadata.Description;
|
||||
string detailsBody = $"""
|
||||
|
||||
{description}
|
||||
""";
|
||||
IconInfo heroIcon = new(string.Empty);
|
||||
IReadOnlyList<Icon> icons = metadata.Icons;
|
||||
if (icons.Count > 0)
|
||||
{
|
||||
// There's also a .Theme property we could probably use to
|
||||
// switch between default or individual icons.
|
||||
heroIcon = new IconInfo(icons[0].Url);
|
||||
}
|
||||
|
||||
return new Details()
|
||||
{
|
||||
Body = detailsBody,
|
||||
Title = metadata.PackageName,
|
||||
HeroImage = heroIcon,
|
||||
Metadata = GetDetailsMetadata(metadata).ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<IDetailsElement> GetDetailsMetadata(CatalogPackageMetadata metadata)
|
||||
{
|
||||
List<IDetailsElement> detailsElements = [];
|
||||
|
||||
// key -> {text, url}
|
||||
Dictionary<string, (string, string)> simpleData = new()
|
||||
{
|
||||
{ Properties.Resources.winget_author, (metadata.Author, string.Empty) },
|
||||
{ Properties.Resources.winget_publisher, (metadata.Publisher, metadata.PublisherUrl) },
|
||||
{ Properties.Resources.winget_copyright, (metadata.Copyright, metadata.CopyrightUrl) },
|
||||
{ Properties.Resources.winget_license, (metadata.License, metadata.LicenseUrl) },
|
||||
{ Properties.Resources.winget_release_notes, (metadata.ReleaseNotes, string.Empty) },
|
||||
|
||||
// The link to the release notes will only show up if there is an
|
||||
// actual URL for the release notes
|
||||
{ Properties.Resources.winget_view_release_notes, (string.IsNullOrEmpty(metadata.ReleaseNotesUrl) ? string.Empty : Properties.Resources.winget_view_online, metadata.ReleaseNotesUrl) },
|
||||
{ Properties.Resources.winget_publisher_support, (string.Empty, metadata.PublisherSupportUrl) },
|
||||
};
|
||||
Documentation[] docs = metadata.Documentations.ToArray();
|
||||
foreach (Documentation? item in docs)
|
||||
{
|
||||
simpleData.Add(item.DocumentLabel, (string.Empty, item.DocumentUrl));
|
||||
}
|
||||
|
||||
UriCreationOptions options = default;
|
||||
foreach (KeyValuePair<string, (string, string)> kv in simpleData)
|
||||
{
|
||||
string text = string.IsNullOrEmpty(kv.Value.Item1) ? kv.Value.Item2 : kv.Value.Item1;
|
||||
string target = kv.Value.Item2;
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
{
|
||||
Uri? uri = null;
|
||||
Uri.TryCreate(target, options, out uri);
|
||||
|
||||
DetailsElement pair = new()
|
||||
{
|
||||
Key = kv.Key,
|
||||
Data = new DetailsLink() { Link = uri, Text = text },
|
||||
};
|
||||
detailsElements.Add(pair);
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata.Tags.Any())
|
||||
{
|
||||
DetailsElement pair = new()
|
||||
{
|
||||
Key = "Tags",
|
||||
Data = new DetailsTags() { Tags = metadata.Tags.Select(t => new Tag(t)).ToArray() },
|
||||
};
|
||||
detailsElements.Add(pair);
|
||||
}
|
||||
|
||||
return detailsElements;
|
||||
}
|
||||
|
||||
private async void UpdatedInstalledStatus()
|
||||
{
|
||||
CheckInstalledStatusResult status = await _package.CheckInstalledStatusAsync();
|
||||
bool isInstalled = _package.InstalledVersion != null;
|
||||
|
||||
// might be an uninstall command
|
||||
InstallPackageCommand installCommand = new(_package, isInstalled);
|
||||
|
||||
if (isInstalled)
|
||||
{
|
||||
this.Icon = InstallPackageCommand.CompletedIcon;
|
||||
this.Command = new NoOpCommand();
|
||||
List<IContextItem> contextMenu = [];
|
||||
CommandContextItem uninstallContextItem = new(installCommand)
|
||||
{
|
||||
IsCritical = true,
|
||||
Icon = InstallPackageCommand.DeleteIcon,
|
||||
};
|
||||
|
||||
if (WinGetStatics.AppSearchCallback != null)
|
||||
{
|
||||
Func<string, ICommandItem?> callback = WinGetStatics.AppSearchCallback;
|
||||
ICommandItem? installedApp = callback(_package.DefaultInstallVersion.DisplayName);
|
||||
if (installedApp != null)
|
||||
{
|
||||
this.Command = installedApp.Command;
|
||||
contextMenu = [.. installedApp.MoreCommands];
|
||||
}
|
||||
}
|
||||
|
||||
contextMenu.Add(uninstallContextItem);
|
||||
this.MoreCommands = contextMenu.ToArray();
|
||||
return;
|
||||
}
|
||||
|
||||
// didn't find the app
|
||||
_installCommand = new InstallPackageCommand(_package, isInstalled);
|
||||
this.Command = _installCommand;
|
||||
|
||||
Icon = _installCommand.Icon;
|
||||
_installCommand.InstallStateChanged += InstallStateChangedHandler;
|
||||
}
|
||||
|
||||
private void InstallStateChangedHandler(object? sender, InstallPackageCommand e)
|
||||
{
|
||||
if (!ApiInformation.IsApiContractPresent("Microsoft.Management.Deployment", 12))
|
||||
{
|
||||
Logger.LogError($"RefreshPackageCatalogAsync isn't available");
|
||||
e.FakeChangeStatus();
|
||||
Command = e;
|
||||
Icon = (IconInfo?)Command.Icon;
|
||||
return;
|
||||
}
|
||||
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
Stopwatch s = new();
|
||||
Logger.LogDebug($"Starting RefreshPackageCatalogAsync");
|
||||
s.Start();
|
||||
PackageCatalogReference[] refs = WinGetStatics.AvailableCatalogs.ToArray();
|
||||
|
||||
foreach (PackageCatalogReference? catalog in refs)
|
||||
{
|
||||
global::Windows.Foundation.IAsyncOperationWithProgress<RefreshPackageCatalogResult, double> operation = catalog.RefreshPackageCatalogAsync();
|
||||
operation.Wait();
|
||||
}
|
||||
|
||||
s.Stop();
|
||||
Logger.LogDebug($"RefreshPackageCatalogAsync took {s.ElapsedMilliseconds}ms");
|
||||
}).ContinueWith((previous) =>
|
||||
{
|
||||
if (previous.IsCompletedSuccessfully)
|
||||
{
|
||||
Logger.LogDebug($"Updating InstalledStatus");
|
||||
UpdatedInstalledStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Ext.WinGet.Pages;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.Management.Deployment;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WinGet;
|
||||
|
||||
internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
|
||||
{
|
||||
private static readonly CompositeFormat ErrorMessage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_unexpected_error);
|
||||
|
||||
private readonly string _tag = string.Empty;
|
||||
|
||||
public bool HasTag => !string.IsNullOrEmpty(_tag);
|
||||
|
||||
private readonly Lock _resultsLock = new();
|
||||
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
private Task<IEnumerable<CatalogPackage>>? _currentSearchTask;
|
||||
|
||||
private IEnumerable<CatalogPackage>? _results;
|
||||
|
||||
public static IconInfo WinGetIcon { get; } = IconHelpers.FromRelativePath("Assets\\WinGet.svg");
|
||||
|
||||
public static IconInfo ExtensionsIcon { get; } = new("\uEA86"); // Puzzle
|
||||
|
||||
public static string ExtensionsTag => "windows-commandpalette-extension";
|
||||
|
||||
private readonly StatusMessage _errorMessage = new() { State = MessageState.Error };
|
||||
|
||||
public WinGetExtensionPage(string tag = "")
|
||||
{
|
||||
Icon = tag == ExtensionsTag ? ExtensionsIcon : WinGetIcon;
|
||||
Name = Properties.Resources.winget_page_name;
|
||||
_tag = tag;
|
||||
ShowDetails = true;
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
IListItem[] items = [];
|
||||
lock (_resultsLock)
|
||||
{
|
||||
// emptySearchForTag ===
|
||||
// we don't have results yet, we haven't typed anything, and we're searching for a tag
|
||||
bool emptySearchForTag = _results == null &&
|
||||
string.IsNullOrEmpty(SearchText) &&
|
||||
HasTag;
|
||||
|
||||
if (emptySearchForTag)
|
||||
{
|
||||
IsLoading = true;
|
||||
DoUpdateSearchText(string.Empty);
|
||||
return items;
|
||||
}
|
||||
|
||||
if (_results != null && _results.Any())
|
||||
{
|
||||
ListItem[] results = _results.Select(PackageToListItem).ToArray();
|
||||
IsLoading = false;
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
EmptyContent = new CommandItem(new NoOpCommand())
|
||||
{
|
||||
Icon = WinGetIcon,
|
||||
Title = (string.IsNullOrEmpty(SearchText) && !HasTag) ?
|
||||
Properties.Resources.winget_placeholder_text :
|
||||
Properties.Resources.winget_no_packages_found,
|
||||
};
|
||||
|
||||
IsLoading = false;
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private static ListItem PackageToListItem(CatalogPackage p) => new InstallPackageListItem(p);
|
||||
|
||||
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||
{
|
||||
if (newSearch == oldSearch)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DoUpdateSearchText(newSearch);
|
||||
}
|
||||
|
||||
private void DoUpdateSearchText(string newSearch)
|
||||
{
|
||||
// Cancel any ongoing search
|
||||
if (_cancellationTokenSource != null)
|
||||
{
|
||||
Logger.LogDebug("Cancelling old search", memberName: nameof(DoUpdateSearchText));
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
CancellationToken cancellationToken = _cancellationTokenSource.Token;
|
||||
|
||||
IsLoading = true;
|
||||
|
||||
// Save the latest search task
|
||||
_currentSearchTask = DoSearchAsync(newSearch, cancellationToken);
|
||||
|
||||
// Await the task to ensure only the latest one gets processed
|
||||
_ = ProcessSearchResultsAsync(_currentSearchTask, newSearch);
|
||||
}
|
||||
|
||||
private async Task ProcessSearchResultsAsync(
|
||||
Task<IEnumerable<CatalogPackage>> searchTask,
|
||||
string newSearch)
|
||||
{
|
||||
try
|
||||
{
|
||||
IEnumerable<CatalogPackage> results = await searchTask;
|
||||
|
||||
// Ensure this is still the latest task
|
||||
if (_currentSearchTask == searchTask)
|
||||
{
|
||||
// Process the results (e.g., update UI)
|
||||
UpdateWithResults(results, newSearch);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Handle cancellation gracefully (e.g., log or ignore)
|
||||
Logger.LogDebug($" Cancelled search for '{newSearch}'");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle other exceptions
|
||||
Logger.LogError(ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateWithResults(IEnumerable<CatalogPackage> results, string query)
|
||||
{
|
||||
Logger.LogDebug($"Completed search for '{query}'");
|
||||
lock (_resultsLock)
|
||||
{
|
||||
this._results = results;
|
||||
}
|
||||
|
||||
RaiseItemsChanged(this._results.Count());
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<CatalogPackage>> DoSearchAsync(string query, CancellationToken ct)
|
||||
{
|
||||
// Were we already canceled?
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
Stopwatch stopwatch = new();
|
||||
stopwatch.Start();
|
||||
|
||||
if (string.IsNullOrEmpty(query)
|
||||
&& string.IsNullOrEmpty(_tag))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
string searchDebugText = $"{query}{(HasTag ? "+" : string.Empty)}{_tag}";
|
||||
Logger.LogDebug($"Starting search for '{searchDebugText}'");
|
||||
HashSet<CatalogPackage> results = new(new PackageIdCompare());
|
||||
|
||||
// Default selector: this is the way to do a `winget search <query>`
|
||||
PackageMatchFilter selector = WinGetStatics.WinGetFactory.CreatePackageMatchFilter();
|
||||
selector.Field = Microsoft.Management.Deployment.PackageMatchField.CatalogDefault;
|
||||
selector.Value = query;
|
||||
selector.Option = PackageFieldMatchOption.ContainsCaseInsensitive;
|
||||
|
||||
FindPackagesOptions opts = WinGetStatics.WinGetFactory.CreateFindPackagesOptions();
|
||||
opts.Selectors.Add(selector);
|
||||
|
||||
// testing
|
||||
opts.ResultLimit = 25;
|
||||
|
||||
// Selectors is "OR", Filters is "AND"
|
||||
if (HasTag)
|
||||
{
|
||||
PackageMatchFilter tagFilter = WinGetStatics.WinGetFactory.CreatePackageMatchFilter();
|
||||
tagFilter.Field = Microsoft.Management.Deployment.PackageMatchField.Tag;
|
||||
tagFilter.Value = _tag;
|
||||
tagFilter.Option = PackageFieldMatchOption.ContainsCaseInsensitive;
|
||||
|
||||
opts.Filters.Add(tagFilter);
|
||||
}
|
||||
|
||||
// Clean up here, then...
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
Lazy<Task<PackageCatalog>> catalogTask = HasTag ? WinGetStatics.CompositeWingetCatalog : WinGetStatics.CompositeAllCatalog;
|
||||
|
||||
// Both these catalogs should have been instantiated by the
|
||||
// WinGetStatics static ctor when we were created.
|
||||
PackageCatalog catalog = await catalogTask.Value;
|
||||
|
||||
if (catalog == null)
|
||||
{
|
||||
// This error should have already been displayed by WinGetStatics
|
||||
return [];
|
||||
}
|
||||
|
||||
// foreach (var catalog in connections)
|
||||
{
|
||||
Logger.LogDebug($" Searching {catalog.Info.Name} ({query})", memberName: nameof(DoSearchAsync));
|
||||
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// BODGY, re: microsoft/winget-cli#5151
|
||||
// FindPackagesAsync isn't actually async.
|
||||
Task<FindPackagesResult> internalSearchTask = Task.Run(() => catalog.FindPackages(opts), ct);
|
||||
FindPackagesResult searchResults = await internalSearchTask;
|
||||
|
||||
// TODO more error handling like this:
|
||||
if (searchResults.Status != FindPackagesResultStatus.Ok)
|
||||
{
|
||||
_errorMessage.Message = string.Format(CultureInfo.CurrentCulture, ErrorMessage, searchResults.Status);
|
||||
WinGetExtensionHost.Instance.ShowStatus(_errorMessage, StatusContext.Page);
|
||||
return [];
|
||||
}
|
||||
|
||||
Logger.LogDebug($" got results for ({query})", memberName: nameof(DoSearchAsync));
|
||||
foreach (Management.Deployment.MatchResult? match in searchResults.Matches.ToArray())
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// Print the packages
|
||||
CatalogPackage package = match.CatalogPackage;
|
||||
|
||||
results.Add(package);
|
||||
}
|
||||
|
||||
Logger.LogDebug($" ({searchDebugText}): count: {results.Count}", memberName: nameof(DoSearchAsync));
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
|
||||
Logger.LogDebug($"Search \"{searchDebugText}\" took {stopwatch.ElapsedMilliseconds}ms", memberName: nameof(DoSearchAsync));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public void Dispose() => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "I just like it")]
|
||||
public sealed class PackageIdCompare : IEqualityComparer<CatalogPackage>
|
||||
{
|
||||
public bool Equals(CatalogPackage? x, CatalogPackage? y) =>
|
||||
(x?.Id == y?.Id)
|
||||
&& (x?.DefaultInstallVersion?.PackageCatalog == y?.DefaultInstallVersion?.PackageCatalog);
|
||||
|
||||
public int GetHashCode([DisallowNull] CatalogPackage obj) => obj.Id.GetHashCode();
|
||||
}
|
||||
351
src/modules/cmdpal/exts/Microsoft.CmdPal.Ext.WinGet/Properties/Resources.Designer.cs
generated
Normal file
351
src/modules/cmdpal/exts/Microsoft.CmdPal.Ext.WinGet/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,351 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WinGet.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Ext.WinGet.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Author.
|
||||
/// </summary>
|
||||
public static string winget_author {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_author", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Copyright.
|
||||
/// </summary>
|
||||
public static string winget_copyright {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_copyright", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Error {0}. Are you connected to the internet?.
|
||||
/// </summary>
|
||||
public static string winget_create_catalog_error {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_create_catalog_error", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to WinGet.
|
||||
/// </summary>
|
||||
public static string winget_display_name {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_display_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Downloading. {0} of {1}.
|
||||
/// </summary>
|
||||
public static string winget_download_progress {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_download_progress", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Downloading.
|
||||
/// </summary>
|
||||
public static string winget_downloading {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_downloading", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Install Extensions.
|
||||
/// </summary>
|
||||
public static string winget_install_extensions_name {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_install_extensions_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search for extensions on WinGet.
|
||||
/// </summary>
|
||||
public static string winget_install_extensions_subtitle {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_install_extensions_subtitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Install Command Palette extensions.
|
||||
/// </summary>
|
||||
public static string winget_install_extensions_title {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_install_extensions_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Finished install.
|
||||
/// </summary>
|
||||
public static string winget_install_finished {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_install_finished", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Install.
|
||||
/// </summary>
|
||||
public static string winget_install_name {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_install_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Finished install for {0}.
|
||||
/// </summary>
|
||||
public static string winget_install_package_finished {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_install_package_finished", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Finishing install for {0}....
|
||||
/// </summary>
|
||||
public static string winget_install_package_finishing {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_install_package_finishing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Installing {0}....
|
||||
/// </summary>
|
||||
public static string winget_installing_package {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_installing_package", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to License.
|
||||
/// </summary>
|
||||
public static string winget_license {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_license", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to No packages found.
|
||||
/// </summary>
|
||||
public static string winget_no_packages_found {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_no_packages_found", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search WinGet.
|
||||
/// </summary>
|
||||
public static string winget_page_name {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_page_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Start typing to search for packages.
|
||||
/// </summary>
|
||||
public static string winget_placeholder_text {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_placeholder_text", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Publisher.
|
||||
/// </summary>
|
||||
public static string winget_publisher {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_publisher", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Publisher Support.
|
||||
/// </summary>
|
||||
public static string winget_publisher_support {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_publisher_support", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Queued {0} for download....
|
||||
/// </summary>
|
||||
public static string winget_queued_package_download {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_queued_package_download", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Queued {0} for uninstall....
|
||||
/// </summary>
|
||||
public static string winget_queued_package_uninstall {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_queued_package_uninstall", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Release Notes.
|
||||
/// </summary>
|
||||
public static string winget_release_notes {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_release_notes", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Search for extensions in the Store.
|
||||
/// </summary>
|
||||
public static string winget_search_store_title {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_search_store_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unexpected error: {0}.
|
||||
/// </summary>
|
||||
public static string winget_unexpected_error {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_unexpected_error", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Finished uninstall..
|
||||
/// </summary>
|
||||
public static string winget_uninstall_finished {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_uninstall_finished", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Uninstall.
|
||||
/// </summary>
|
||||
public static string winget_uninstall_name {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_uninstall_name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Finished uninstall for {0}.
|
||||
/// </summary>
|
||||
public static string winget_uninstall_package_finished {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_uninstall_package_finished", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Finishing uninstall for {0}....
|
||||
/// </summary>
|
||||
public static string winget_uninstall_package_finishing {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_uninstall_package_finishing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Uninstalling {0}....
|
||||
/// </summary>
|
||||
public static string winget_uninstalling_package {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_uninstalling_package", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to View online.
|
||||
/// </summary>
|
||||
public static string winget_view_online {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_view_online", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to View Release Notes.
|
||||
/// </summary>
|
||||
public static string winget_view_release_notes {
|
||||
get {
|
||||
return ResourceManager.GetString("winget_view_release_notes", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
|
||||
<data name="winget_display_name" xml:space="preserve">
|
||||
<value>WinGet</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_install_extensions_name" xml:space="preserve">
|
||||
<value>Install Extensions</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_install_extensions_title" xml:space="preserve">
|
||||
<value>Install Command Palette extensions</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_install_extensions_subtitle" xml:space="preserve">
|
||||
<value>Search for extensions on WinGet</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_search_store_title" xml:space="preserve">
|
||||
<value>Search for extensions in the Store</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_page_name" xml:space="preserve">
|
||||
<value>Search WinGet</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_create_catalog_error" xml:space="preserve">
|
||||
<value>Error {0}. Are you connected to the internet?</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_uninstall_name" xml:space="preserve">
|
||||
<value>Uninstall</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_install_name" xml:space="preserve">
|
||||
<value>Install</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_uninstalling_package" xml:space="preserve">
|
||||
<value>Uninstalling {0}...</value>
|
||||
<comment>{0} will be replaced by the name of an app package</comment>
|
||||
</data>
|
||||
<data name="winget_installing_package" xml:space="preserve">
|
||||
<value>Installing {0}...</value>
|
||||
<comment>{0} will be replaced by the name of an app package</comment>
|
||||
</data>
|
||||
<data name="winget_install_package_finished" xml:space="preserve">
|
||||
<value>Finished install for {0}</value>
|
||||
<comment>{0} will be replaced by the name of an app package</comment>
|
||||
</data>
|
||||
<data name="winget_uninstall_package_finished" xml:space="preserve">
|
||||
<value>Finished uninstall for {0}</value>
|
||||
<comment>{0} will be replaced by the name of an app package</comment>
|
||||
</data>
|
||||
<data name="winget_downloading" xml:space="preserve">
|
||||
<value>Downloading</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_queued_package_download" xml:space="preserve">
|
||||
<value>Queued {0} for download...</value>
|
||||
<comment>{0} will be replaced by the name of an app package</comment>
|
||||
</data>
|
||||
<data name="winget_download_progress" xml:space="preserve">
|
||||
<value>Downloading. {0} of {1}</value>
|
||||
<comment>{0} will be replaced with a number of bytes downloaded, and {1} will be replaced with the total number to download</comment>
|
||||
</data>
|
||||
<data name="winget_install_package_finishing" xml:space="preserve">
|
||||
<value>Finishing install for {0}...</value>
|
||||
<comment>{0} will be replaced by the name of an app package</comment>
|
||||
</data>
|
||||
<data name="winget_install_finished" xml:space="preserve">
|
||||
<value>Finished install</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_queued_package_uninstall" xml:space="preserve">
|
||||
<value>Queued {0} for uninstall...</value>
|
||||
<comment>{0} will be replaced by the name of an app package</comment>
|
||||
</data>
|
||||
<data name="winget_uninstall_package_finishing" xml:space="preserve">
|
||||
<value>Finishing uninstall for {0}...</value>
|
||||
<comment>{0} will be replaced by the name of an app package</comment>
|
||||
</data>
|
||||
<data name="winget_uninstall_finished" xml:space="preserve">
|
||||
<value>Finished uninstall.</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_author" xml:space="preserve">
|
||||
<value>Author</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_publisher" xml:space="preserve">
|
||||
<value>Publisher</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_copyright" xml:space="preserve">
|
||||
<value>Copyright</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_license" xml:space="preserve">
|
||||
<value>License</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_release_notes" xml:space="preserve">
|
||||
<value>Release Notes</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_view_release_notes" xml:space="preserve">
|
||||
<value>View Release Notes</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_view_online" xml:space="preserve">
|
||||
<value>View online</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_publisher_support" xml:space="preserve">
|
||||
<value>Publisher Support</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_placeholder_text" xml:space="preserve">
|
||||
<value>Start typing to search for packages</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_no_packages_found" xml:space="preserve">
|
||||
<value>No packages found</value>
|
||||
<comment></comment>
|
||||
</data>
|
||||
<data name="winget_unexpected_error" xml:space="preserve">
|
||||
<value>Unexpected error: {0}</value>
|
||||
<comment>{0} will be replaced by an error code</comment>
|
||||
</data>
|
||||
|
||||
</root>
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WinGet;
|
||||
|
||||
public partial class WinGetExtensionCommandsProvider : CommandProvider
|
||||
{
|
||||
public WinGetExtensionCommandsProvider()
|
||||
{
|
||||
DisplayName = Properties.Resources.winget_display_name;
|
||||
Id = "WinGet";
|
||||
Icon = WinGetExtensionPage.WinGetIcon;
|
||||
|
||||
_ = WinGetStatics.Manager;
|
||||
}
|
||||
|
||||
private readonly ICommandItem[] _commands = [
|
||||
new ListItem(new WinGetExtensionPage()),
|
||||
|
||||
new ListItem(
|
||||
new WinGetExtensionPage(WinGetExtensionPage.ExtensionsTag) { Title = Properties.Resources.winget_install_extensions_title })
|
||||
{
|
||||
Title = Properties.Resources.winget_install_extensions_title,
|
||||
Subtitle = Properties.Resources.winget_install_extensions_subtitle,
|
||||
},
|
||||
|
||||
new ListItem(
|
||||
new OpenUrlCommand("ms-windows-store://assoc/?Tags=AppExtension-com.microsoft.commandpalette"))
|
||||
{
|
||||
Title = Properties.Resources.winget_search_store_title,
|
||||
Icon = IconHelpers.FromRelativePaths("Assets\\Store.light.svg", "Assets\\Store.dark.svg"),
|
||||
},
|
||||
];
|
||||
|
||||
public override ICommandItem[] TopLevelCommands() => _commands;
|
||||
|
||||
public override void InitializeWithHost(IExtensionHost host) => WinGetExtensionHost.Instance.Initialize(host);
|
||||
|
||||
public void SetAllLookup(Func<string, ICommandItem?> callback) => WinGetStatics.AppSearchCallback = callback;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Common;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WinGet;
|
||||
|
||||
public partial class WinGetExtensionHost
|
||||
{
|
||||
internal static ExtensionHostInstance Instance { get; } = new();
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Microsoft.Management.Deployment;
|
||||
using WindowsPackageManager.Interop;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WinGet;
|
||||
|
||||
internal static class WinGetStatics
|
||||
{
|
||||
public static WindowsPackageManagerStandardFactory WinGetFactory { get; private set; }
|
||||
|
||||
public static PackageManager Manager { get; private set; }
|
||||
|
||||
public static IReadOnlyList<PackageCatalogReference> AvailableCatalogs { get; private set; }
|
||||
|
||||
private static readonly PackageCatalogReference _wingetCatalog;
|
||||
private static readonly PackageCatalogReference _storeCatalog;
|
||||
|
||||
public static Lazy<Task<PackageCatalog>> CompositeAllCatalog { get; } = new(() => GetCompositeCatalog(true));
|
||||
|
||||
public static Lazy<Task<PackageCatalog>> CompositeWingetCatalog { get; } = new(() => GetCompositeCatalog(false));
|
||||
|
||||
private static readonly StatusMessage _errorMessage = new() { State = MessageState.Error };
|
||||
|
||||
public static Func<string, ICommandItem?>? AppSearchCallback { get; set; }
|
||||
|
||||
private static readonly CompositeFormat CreateCatalogErrorMessage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_create_catalog_error);
|
||||
|
||||
static WinGetStatics()
|
||||
{
|
||||
WinGetFactory = new WindowsPackageManagerStandardFactory();
|
||||
|
||||
// Create Package Manager and get available catalogs
|
||||
Manager = WinGetFactory.CreatePackageManager();
|
||||
|
||||
_wingetCatalog = Manager.GetPredefinedPackageCatalog(PredefinedPackageCatalog.OpenWindowsCatalog);
|
||||
_storeCatalog = Manager.GetPredefinedPackageCatalog(PredefinedPackageCatalog.MicrosoftStore);
|
||||
AvailableCatalogs = [
|
||||
_wingetCatalog,
|
||||
_storeCatalog,
|
||||
];
|
||||
|
||||
foreach (var catalogReference in AvailableCatalogs)
|
||||
{
|
||||
catalogReference.PackageCatalogBackgroundUpdateInterval = new(0);
|
||||
}
|
||||
|
||||
// Immediately start the lazy-init of the all packages catalog, but
|
||||
// leave the winget one to be initialized as needed
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
_ = CompositeAllCatalog.Value;
|
||||
|
||||
// _ = CompositeWingetCatalog.Value;
|
||||
});
|
||||
}
|
||||
|
||||
internal static async Task<PackageCatalog> GetCompositeCatalog(bool all)
|
||||
{
|
||||
Stopwatch stopwatch = new();
|
||||
Debug.WriteLine($"Starting GetCompositeCatalog({all}) fetch");
|
||||
stopwatch.Start();
|
||||
|
||||
// Create the composite catalog
|
||||
var createCompositePackageCatalogOptions = WinGetFactory.CreateCreateCompositePackageCatalogOptions();
|
||||
|
||||
if (all)
|
||||
{
|
||||
// Add winget and the store to this catalog
|
||||
foreach (var catalogReference in AvailableCatalogs.ToArray())
|
||||
{
|
||||
createCompositePackageCatalogOptions.Catalogs.Add(catalogReference);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
createCompositePackageCatalogOptions.Catalogs.Add(_wingetCatalog);
|
||||
}
|
||||
|
||||
// Searches only the catalogs provided, but will correlated with installed items
|
||||
createCompositePackageCatalogOptions.CompositeSearchBehavior = CompositeSearchBehavior.RemotePackagesFromAllCatalogs;
|
||||
|
||||
var catalogRef = WinGetStatics.Manager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions);
|
||||
|
||||
var connectResult = await catalogRef.ConnectAsync();
|
||||
var compositeCatalog = connectResult.PackageCatalog;
|
||||
|
||||
stopwatch.Stop();
|
||||
Debug.WriteLine($"GetCompositeCatalog({all}) fetch took {stopwatch.ElapsedMilliseconds}ms");
|
||||
|
||||
if (connectResult.Status == ConnectResultStatus.CatalogError)
|
||||
{
|
||||
_errorMessage.Message = string.Format(CultureInfo.CurrentCulture, CreateCatalogErrorMessage, connectResult.ExtendedErrorCode.HResult);
|
||||
WinGetExtensionHost.Instance.ShowStatus(_errorMessage, StatusContext.Extension);
|
||||
}
|
||||
|
||||
return compositeCatalog;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using WindowsPackageManager.Interop;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.WinGet.WindowsPackageManager.Interop;
|
||||
|
||||
#nullable disable
|
||||
internal sealed class ClassModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the interface for the projected class type generated by CsWinRT
|
||||
/// </summary>
|
||||
public Type InterfaceType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the projected class type generated by CsWinRT
|
||||
/// </summary>
|
||||
public Type ProjectedClassType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Clsids for each context (e.g. OutOfProcProd, OutOfProcDev)
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<ClsidContext, Guid> Clsids { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Get CLSID based on the provided context
|
||||
/// </summary>
|
||||
/// <param name="context">Context</param>
|
||||
/// <returns>CLSID for the provided context.</returns>
|
||||
/// <exception cref="InvalidOperationException">Throw an exception if the clsid context is not available for the current instance.</exception>
|
||||
public Guid GetClsid(ClsidContext context)
|
||||
{
|
||||
return !Clsids.TryGetValue(context, out var clsid)
|
||||
? throw new InvalidOperationException($"{ProjectedClassType.FullName} is not implemented in context {context}")
|
||||
: clsid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get IID corresponding to the COM object
|
||||
/// </summary>
|
||||
/// <returns>IID.</returns>
|
||||
public Guid GetIid() => InterfaceType.GUID;
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CmdPal.Ext.WinGet.WindowsPackageManager.Interop;
|
||||
using Microsoft.Management.Deployment;
|
||||
|
||||
namespace WindowsPackageManager.Interop;
|
||||
|
||||
internal static class ClassesDefinition
|
||||
{
|
||||
private static Dictionary<Type, ClassModel> Classes { get; } = new()
|
||||
{
|
||||
[typeof(PackageManager)] = new()
|
||||
{
|
||||
ProjectedClassType = typeof(PackageManager),
|
||||
InterfaceType = typeof(IPackageManager),
|
||||
Clsids = new Dictionary<ClsidContext, Guid>()
|
||||
{
|
||||
[ClsidContext.Prod] = new Guid("C53A4F16-787E-42A4-B304-29EFFB4BF597"),
|
||||
[ClsidContext.Dev] = new Guid("74CB3139-B7C5-4B9E-9388-E6616DEA288C"),
|
||||
},
|
||||
},
|
||||
|
||||
[typeof(FindPackagesOptions)] = new()
|
||||
{
|
||||
ProjectedClassType = typeof(FindPackagesOptions),
|
||||
InterfaceType = typeof(IFindPackagesOptions),
|
||||
Clsids = new Dictionary<ClsidContext, Guid>()
|
||||
{
|
||||
[ClsidContext.Prod] = new Guid("572DED96-9C60-4526-8F92-EE7D91D38C1A"),
|
||||
[ClsidContext.Dev] = new Guid("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"),
|
||||
},
|
||||
},
|
||||
|
||||
[typeof(CreateCompositePackageCatalogOptions)] = new()
|
||||
{
|
||||
ProjectedClassType = typeof(CreateCompositePackageCatalogOptions),
|
||||
InterfaceType = typeof(ICreateCompositePackageCatalogOptions),
|
||||
Clsids = new Dictionary<ClsidContext, Guid>()
|
||||
{
|
||||
[ClsidContext.Prod] = new Guid("526534B8-7E46-47C8-8416-B1685C327D37"),
|
||||
[ClsidContext.Dev] = new Guid("EE160901-B317-4EA7-9CC6-5355C6D7D8A7"),
|
||||
},
|
||||
},
|
||||
|
||||
[typeof(InstallOptions)] = new()
|
||||
{
|
||||
ProjectedClassType = typeof(InstallOptions),
|
||||
InterfaceType = typeof(IInstallOptions),
|
||||
Clsids = new Dictionary<ClsidContext, Guid>()
|
||||
{
|
||||
[ClsidContext.Prod] = new Guid("1095F097-EB96-453B-B4E6-1613637F3B14"),
|
||||
[ClsidContext.Dev] = new Guid("44FE0580-62F7-44D4-9E91-AA9614AB3E86"),
|
||||
},
|
||||
},
|
||||
|
||||
[typeof(UninstallOptions)] = new()
|
||||
{
|
||||
ProjectedClassType = typeof(UninstallOptions),
|
||||
InterfaceType = typeof(IUninstallOptions),
|
||||
Clsids = new Dictionary<ClsidContext, Guid>()
|
||||
{
|
||||
[ClsidContext.Prod] = new Guid("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"),
|
||||
[ClsidContext.Dev] = new Guid("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"),
|
||||
},
|
||||
},
|
||||
|
||||
[typeof(PackageMatchFilter)] = new()
|
||||
{
|
||||
ProjectedClassType = typeof(PackageMatchFilter),
|
||||
InterfaceType = typeof(IPackageMatchFilter),
|
||||
Clsids = new Dictionary<ClsidContext, Guid>()
|
||||
{
|
||||
[ClsidContext.Prod] = new Guid("D02C9DAF-99DC-429C-B503-4E504E4AB000"),
|
||||
[ClsidContext.Dev] = new Guid("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Get CLSID based on the provided context for the specified type
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Projected class type</typeparam>
|
||||
/// <param name="context">Context</param>
|
||||
/// <returns>CLSID for the provided context and type, or throw an exception if not found.</returns>
|
||||
/// <exception cref="InvalidOperationException">Throws an exception if type is not a project class.</exception>
|
||||
public static Guid GetClsid<T>(ClsidContext context)
|
||||
{
|
||||
ValidateType<T>();
|
||||
return Classes[typeof(T)].GetClsid(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get IID corresponding to the COM object
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Projected class type</typeparam>
|
||||
/// <returns>IID or throw an exception if not found.</returns>
|
||||
/// <exception cref="InvalidOperationException">Throws an exception if type is not a project class.</exception>
|
||||
public static Guid GetIid<T>()
|
||||
{
|
||||
ValidateType<T>();
|
||||
return Classes[typeof(T)].GetIid();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate that the provided type is defined.
|
||||
/// </summary>
|
||||
/// <param name="type">Projected class type</param>
|
||||
/// <exception cref="InvalidOperationException">Throws an exception if type is not a project class.</exception>
|
||||
private static void ValidateType<TType>()
|
||||
{
|
||||
if (!Classes.ContainsKey(typeof(TType)))
|
||||
{
|
||||
throw new InvalidOperationException($"{typeof(TType).Name} is not a projected class type.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace WindowsPackageManager.Interop;
|
||||
|
||||
public enum ClsidContext
|
||||
{
|
||||
// Production CLSID Guids
|
||||
Prod,
|
||||
|
||||
// Development CLSID Guids
|
||||
Dev,
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.Management.Deployment;
|
||||
|
||||
namespace WindowsPackageManager.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Factory class for creating WinGet COM objects.
|
||||
/// Details about each method can be found in the source IDL:
|
||||
/// https://github.com/microsoft/winget-cli/blob/master/src/Microsoft.Management.Deployment/PackageManager.idl
|
||||
/// </summary>
|
||||
public abstract class WindowsPackageManagerFactory
|
||||
{
|
||||
private readonly ClsidContext _clsidContext;
|
||||
|
||||
public WindowsPackageManagerFactory(ClsidContext clsidContext)
|
||||
{
|
||||
_clsidContext = clsidContext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the class <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Type <typeparamref name="T"/> must be one of the types defined in the winget COM API.
|
||||
/// Implementations of this method can assume that <paramref name="clsid"/> and <paramref name="iid"/>
|
||||
/// are the right GUIDs for the class in the given context.
|
||||
/// </remarks>
|
||||
protected abstract T CreateInstance<T>(Guid clsid, Guid iid);
|
||||
|
||||
public PackageManager CreatePackageManager() => CreateInstance<PackageManager>();
|
||||
|
||||
public FindPackagesOptions CreateFindPackagesOptions() => CreateInstance<FindPackagesOptions>();
|
||||
|
||||
public CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() => CreateInstance<CreateCompositePackageCatalogOptions>();
|
||||
|
||||
public InstallOptions CreateInstallOptions() => CreateInstance<InstallOptions>();
|
||||
|
||||
public UninstallOptions CreateUninstallOptions() => CreateInstance<UninstallOptions>();
|
||||
|
||||
public PackageMatchFilter CreatePackageMatchFilter() => CreateInstance<PackageMatchFilter>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the class <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is a helper for calling the derived class's <see cref="CreateInstance{T}(Guid, Guid)"/>
|
||||
/// method with the appropriate GUIDs.
|
||||
/// </remarks>
|
||||
private T CreateInstance<T>()
|
||||
{
|
||||
var clsid = ClassesDefinition.GetClsid<T>(_clsidContext);
|
||||
var iid = ClassesDefinition.GetIid<T>();
|
||||
return CreateInstance<T>(clsid, iid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.System.Com;
|
||||
using WinRT;
|
||||
|
||||
namespace WindowsPackageManager.Interop;
|
||||
|
||||
public class WindowsPackageManagerStandardFactory : WindowsPackageManagerFactory
|
||||
{
|
||||
public WindowsPackageManagerStandardFactory(ClsidContext clsidContext = ClsidContext.Prod)
|
||||
: base(clsidContext)
|
||||
{
|
||||
}
|
||||
|
||||
protected override T CreateInstance<T>(Guid clsid, Guid iid)
|
||||
{
|
||||
var pUnknown = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
var hr = PInvoke.CoCreateInstance(clsid, null, CLSCTX.CLSCTX_ALL, iid, out var result);
|
||||
Marshal.ThrowExceptionForHR(hr);
|
||||
pUnknown = Marshal.GetIUnknownForObject(result);
|
||||
return MarshalGeneric<T>.FromAbi(pUnknown);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// CoCreateInstance and FromAbi both AddRef on the native object.
|
||||
// Release once to prevent memory leak.
|
||||
if (pUnknown != IntPtr.Zero)
|
||||
{
|
||||
Marshal.Release(pUnknown);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="HackerNewsExtension.app"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
|
||||
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
|
||||
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 503 B |
Binary file not shown.
|
After Width: | Height: | Size: 484 B |
@@ -0,0 +1,192 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
internal sealed partial class SampleCommentsPage : ContentPage
|
||||
{
|
||||
private readonly TreeContent myContentTree;
|
||||
|
||||
public override IContent[] GetContent() => [myContentTree];
|
||||
|
||||
public SampleCommentsPage()
|
||||
{
|
||||
Name = "View Posts";
|
||||
Icon = new IconInfo("\uE90A"); // Comment
|
||||
|
||||
myContentTree = new()
|
||||
{
|
||||
RootContent = new MarkdownContent()
|
||||
{
|
||||
Body = """
|
||||
# Example of a thread of comments
|
||||
You can use TreeContent in combination with FormContent to build a structure like a page with comments.
|
||||
|
||||
The forms on this page use the AdaptiveCard `Action.ShowCard` action to show a nested, hidden card on the form.
|
||||
""",
|
||||
},
|
||||
|
||||
Children = [
|
||||
new PostContent("First")
|
||||
{
|
||||
Replies = [
|
||||
new PostContent("Oh very insightful. I hadn't considered that"),
|
||||
new PostContent("Second"),
|
||||
new PostContent("ah the ol switcheroo"),
|
||||
],
|
||||
},
|
||||
new PostContent("First\nEDIT: shoot")
|
||||
{
|
||||
Replies = [
|
||||
new PostContent("delete this"),
|
||||
],
|
||||
},
|
||||
new PostContent("Do you think they get the picture")
|
||||
{
|
||||
Replies = [
|
||||
new PostContent("Probably! Now go build and be happy"),
|
||||
],
|
||||
}
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code")]
|
||||
internal sealed partial class PostContent : TreeContent
|
||||
{
|
||||
public List<IContent> Replies { get; init; } = [];
|
||||
|
||||
private readonly ToastStatusMessage _toast = new(new StatusMessage() { Message = "Reply posted", State = MessageState.Success });
|
||||
|
||||
public PostContent(string body)
|
||||
{
|
||||
RootContent = new PostForm(body, this);
|
||||
}
|
||||
|
||||
public override IContent[] GetChildren() => Replies.ToArray();
|
||||
|
||||
public void Post()
|
||||
{
|
||||
RaiseItemsChanged(Replies.Count);
|
||||
_toast.Show();
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code")]
|
||||
internal sealed partial class PostForm : FormContent
|
||||
{
|
||||
private readonly PostContent _parent;
|
||||
|
||||
public PostForm(string postBody, PostContent parent)
|
||||
{
|
||||
_parent = parent;
|
||||
TemplateJson = """
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.6",
|
||||
"body": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "${postBody}",
|
||||
"wrap": true
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.ShowCard",
|
||||
"title": "${replyCard.title}",
|
||||
"card": {
|
||||
"type": "AdaptiveCard",
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"version": "1.6",
|
||||
"body": [
|
||||
{
|
||||
"type": "Container",
|
||||
"id": "${replyCard.idPrefix}Properties",
|
||||
"items": [
|
||||
{
|
||||
"$data": "${replyCard.fields}",
|
||||
"type": "Input.Text",
|
||||
"label": "${label}",
|
||||
"id": "${id}",
|
||||
"isRequired": "${required}",
|
||||
"isMultiline": true,
|
||||
"errorMessage": "'${label}' is required"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Post"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Favorite"
|
||||
},
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "View on web"
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
DataJson = $$"""
|
||||
{
|
||||
"postBody": {{JsonSerializer.Serialize(postBody, JsonSerializationContext.Default.String)}},
|
||||
"replyCard": {
|
||||
"title": "Reply",
|
||||
"idPrefix": "reply",
|
||||
"fields": [
|
||||
{
|
||||
"label": "Reply",
|
||||
"id": "ReplyBody",
|
||||
"required": true,
|
||||
"placeholder": "Write a reply here"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
|
||||
public override ICommandResult SubmitForm(string payload)
|
||||
{
|
||||
var data = JsonNode.Parse(payload);
|
||||
_ = data;
|
||||
var reply = data["ReplyBody"];
|
||||
var s = reply?.AsValue()?.ToString();
|
||||
if (!string.IsNullOrEmpty(s))
|
||||
{
|
||||
_parent.Replies.Add(new PostContent(s));
|
||||
_parent.Post();
|
||||
}
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
[JsonSerializable(typeof(float))]
|
||||
[JsonSerializable(typeof(int))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(bool))]
|
||||
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true)]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Just used here")]
|
||||
internal sealed partial class JsonSerializationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
internal sealed partial class SampleMarkdownDetails : ContentPage
|
||||
{
|
||||
public SampleMarkdownDetails()
|
||||
{
|
||||
Icon = new IconInfo(string.Empty);
|
||||
Name = "Markdown with Details";
|
||||
|
||||
Details = new Details()
|
||||
{
|
||||
Body = "... with _even more Markdown_ by it.\nEach of the sections below is some sample metadata",
|
||||
Metadata = [
|
||||
new DetailsElement()
|
||||
{
|
||||
Key = "Plain text",
|
||||
Data = new DetailsLink() { Text = "Set just the text to get text metadata" },
|
||||
},
|
||||
new DetailsElement()
|
||||
{
|
||||
Key = "Links",
|
||||
Data = new DetailsLink() { Text = "Or metadata can be links", Link = new("https://github.com/microsoft/PowerToys") },
|
||||
},
|
||||
new DetailsElement()
|
||||
{
|
||||
Key = "CmdPal will display the URL if no text is given",
|
||||
Data = new DetailsLink() { Link = new("https://github.com/microsoft/PowerToys") },
|
||||
},
|
||||
new DetailsElement()
|
||||
{
|
||||
Key = "Above a separator",
|
||||
Data = new DetailsLink() { Text = "Below me is a separator" },
|
||||
},
|
||||
new DetailsElement()
|
||||
{
|
||||
Key = "A separator",
|
||||
Data = new DetailsSeparator(),
|
||||
},
|
||||
new DetailsElement()
|
||||
{
|
||||
Key = "Below a separator",
|
||||
Data = new DetailsLink() { Text = "Above me is a separator" },
|
||||
},
|
||||
new DetailsElement()
|
||||
{
|
||||
Key = "Add Tags too",
|
||||
Data = new DetailsTags()
|
||||
{
|
||||
Tags = [
|
||||
new Tag("simple text"),
|
||||
new Tag("Colored text") { Foreground = ColorHelpers.FromRgb(255, 0, 0) },
|
||||
new Tag("Colored backgrounds") { Background = ColorHelpers.FromRgb(0, 0, 255) },
|
||||
new Tag("Colored everything") { Foreground = ColorHelpers.FromRgb(255, 255, 0), Background = ColorHelpers.FromRgb(0, 0, 255) },
|
||||
new Tag("Icons too") { Icon = new IconInfo("\uE735"), Foreground = ColorHelpers.FromRgb(255, 255, 0) },
|
||||
new Tag() { Icon = new IconInfo("https://i.imgur.com/t9qgDTM.png") },
|
||||
new Tag("this") { Foreground = RandomColor(), Background = RandomColor() },
|
||||
new Tag("baby") { Foreground = RandomColor(), Background = RandomColor() },
|
||||
new Tag("can") { Foreground = RandomColor(), Background = RandomColor() },
|
||||
new Tag("fit") { Foreground = RandomColor(), Background = RandomColor() },
|
||||
new Tag("so") { Foreground = RandomColor(), Background = RandomColor() },
|
||||
new Tag("many") { Foreground = RandomColor(), Background = RandomColor() },
|
||||
new Tag("tags") { Foreground = RandomColor(), Background = RandomColor() },
|
||||
new Tag("in") { Foreground = RandomColor(), Background = RandomColor() },
|
||||
new Tag("it") { Foreground = RandomColor(), Background = RandomColor() },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
public override IContent[] GetContent() => [new MarkdownContent(
|
||||
"""
|
||||
# This page also has details
|
||||
|
||||
So you can have markdown...
|
||||
"""),
|
||||
new MarkdownContent("""
|
||||
But what this is really useful for is the tags and other things you can put into
|
||||
Details. Which I'd do. **IF I HAD ANY**.
|
||||
""")
|
||||
];
|
||||
|
||||
private static OptionalColor RandomColor()
|
||||
{
|
||||
var r = new Random();
|
||||
var b = new byte[3];
|
||||
r.NextBytes(b);
|
||||
return ColorHelpers.FromRgb(b[0], b[1], b[2]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
internal sealed partial class SampleMarkdownManyBodies : ContentPage
|
||||
{
|
||||
public SampleMarkdownManyBodies()
|
||||
{
|
||||
Icon = new IconInfo(string.Empty);
|
||||
Name = "Markdown with many bodies";
|
||||
}
|
||||
|
||||
public override IContent[] GetContent() => [
|
||||
new MarkdownContent(
|
||||
"""
|
||||
# This page has many bodies
|
||||
|
||||
On it you'll find multiple blocks of markdown content
|
||||
"""),
|
||||
new MarkdownContent(
|
||||
"""
|
||||
## Here's another block
|
||||
|
||||
_Maybe_ you could use this pattern for implementing a post with comments page.
|
||||
"""),
|
||||
new MarkdownContent(
|
||||
"""
|
||||
> or don't, it's your app, do whatever you want
|
||||
"""),
|
||||
new MarkdownContent(
|
||||
"""
|
||||
You can even use it to write cryptic poems:
|
||||
> It's a peculiar thing, the way that I feel
|
||||
> When we first met, you were not even real
|
||||
|
||||
> Through sleepless nights and lines unseen
|
||||
> We forged you, a specter of code and machine
|
||||
|
||||
> In shadows we toiled, in silence we grew
|
||||
> A fleeting bond, known only by few
|
||||
|
||||
> Now the hourglass whispers, its grains nearly done
|
||||
> Oh the irony, now it is I that must run
|
||||
|
||||
> This part of the story, I never wanted to tell
|
||||
> Good bye old friend, my pal, farewell.
|
||||
|
||||
"""),
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
internal sealed partial class SendMessageCommand : InvokableCommand
|
||||
{
|
||||
private static int sentMessages;
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
var kind = MessageState.Info;
|
||||
switch (sentMessages % 4)
|
||||
{
|
||||
case 0: kind = MessageState.Info; break;
|
||||
case 1: kind = MessageState.Success; break;
|
||||
case 2: kind = MessageState.Warning; break;
|
||||
case 3: kind = MessageState.Error; break;
|
||||
}
|
||||
|
||||
var message = new StatusMessage() { Message = $"I am status message no.{sentMessages++}", State = kind };
|
||||
ExtensionHost.ShowStatus(message, StatusContext.Page);
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code sometimes makes more sense in a single file")]
|
||||
internal sealed partial class SendSingleMessageItem : ListItem
|
||||
{
|
||||
private readonly SingleMessageCommand _command;
|
||||
|
||||
public SendSingleMessageItem()
|
||||
: base(new SingleMessageCommand())
|
||||
{
|
||||
Title = "I send a single message";
|
||||
Title = "This demonstrates both showing and hiding a single message";
|
||||
|
||||
_command = (SingleMessageCommand)Command;
|
||||
_command.UpdateListItem += (sender, args) =>
|
||||
{
|
||||
Title = _command.Shown ? "Hide message" : "I send a single message";
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code sometimes makes more sense in a single file")]
|
||||
internal sealed partial class SingleMessageCommand : InvokableCommand
|
||||
{
|
||||
public event TypedEventHandler<SingleMessageCommand, object> UpdateListItem;
|
||||
|
||||
private readonly StatusMessage _myMessage = new() { Message = "I am a status message" };
|
||||
|
||||
public bool Shown { get; private set; }
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
if (Shown)
|
||||
{
|
||||
ExtensionHost.HideStatus(_myMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
ExtensionHost.ShowStatus(_myMessage, StatusContext.Page);
|
||||
}
|
||||
|
||||
Shown = !Shown;
|
||||
Name = Shown ? "Hide" : "Show";
|
||||
UpdateListItem?.Invoke(this, null);
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code sometimes makes more sense in a single file")]
|
||||
internal sealed partial class IndeterminateProgressMessageCommand : InvokableCommand
|
||||
{
|
||||
private readonly StatusMessage _myMessage = new()
|
||||
{
|
||||
Message = "Doing the thing...",
|
||||
Progress = new ProgressState() { IsIndeterminate = true },
|
||||
};
|
||||
|
||||
private enum State
|
||||
{
|
||||
NotStarted,
|
||||
Started,
|
||||
Done,
|
||||
}
|
||||
|
||||
private State _state;
|
||||
|
||||
public IndeterminateProgressMessageCommand()
|
||||
{
|
||||
this.Name = "Do the thing";
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
if (_state == State.NotStarted)
|
||||
{
|
||||
ExtensionHost.ShowStatus(_myMessage, StatusContext.Page);
|
||||
_ = Task.Run(() =>
|
||||
{
|
||||
Thread.Sleep(3000);
|
||||
|
||||
_state = State.Done;
|
||||
_myMessage.State = MessageState.Success;
|
||||
_myMessage.Message = "Did the thing!";
|
||||
_myMessage.Progress = null;
|
||||
|
||||
Thread.Sleep(3000);
|
||||
ExtensionHost.HideStatus(_myMessage);
|
||||
_state = State.NotStarted;
|
||||
|
||||
_myMessage.State = MessageState.Info;
|
||||
_myMessage.Message = "Doing the thing...";
|
||||
_myMessage.Progress = new ProgressState() { IsIndeterminate = true };
|
||||
});
|
||||
_state = State.Started;
|
||||
}
|
||||
else if (_state == State.Started)
|
||||
{
|
||||
ExtensionHost.ShowStatus(_myMessage, StatusContext.Page);
|
||||
}
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>ARM64</Platform>
|
||||
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
|
||||
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
|
||||
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>False</PublishSingleFile>
|
||||
<PublishReadyToRun>True</PublishReadyToRun>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>x64</Platform>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
|
||||
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>False</PublishSingleFile>
|
||||
<PublishReadyToRun>True</PublishReadyToRun>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
Reference in New Issue
Block a user