mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 03:37:59 +01:00
CmdPal: New Remote Desktop built-in extension (#43090)
This PR introduces a new built-in extension for Remote Desktop users. It allows you to view past RDP connections, save predefined connections, and connect to any of them. Or start a new RDP connection. https://github.com/user-attachments/assets/6a5041a6-5741-4df0-a305-da7166f962e1 ### GitHub issue maintenance stuff Closes #38305 --------- Co-authored-by: Niels Laute <niels.laute@live.nl> Co-authored-by: Jiří Polášek <me@jiripolasek.com>
This commit is contained in:
8
.github/actions/spell-check/expect.txt
vendored
8
.github/actions/spell-check/expect.txt
vendored
@@ -727,7 +727,6 @@ HWNDPARENT
|
||||
HWNDPREV
|
||||
hyjiacan
|
||||
IAI
|
||||
icf
|
||||
ICONERROR
|
||||
ICONLOCATION
|
||||
IDCANCEL
|
||||
@@ -800,6 +799,7 @@ invokecommand
|
||||
ipcmanager
|
||||
IPREVIEW
|
||||
ipreviewhandlervisualssetfont
|
||||
irdp
|
||||
IPTC
|
||||
irow
|
||||
irprops
|
||||
@@ -1057,6 +1057,7 @@ msrc
|
||||
msstore
|
||||
msvcp
|
||||
MT
|
||||
mstsc
|
||||
MTND
|
||||
MULTIPLEUSE
|
||||
multizone
|
||||
@@ -1206,8 +1207,11 @@ OOBEUI
|
||||
openas
|
||||
opencode
|
||||
OPENFILENAME
|
||||
openrdp
|
||||
opensource
|
||||
openxmlformats
|
||||
ollama
|
||||
onnx
|
||||
OPTIMIZEFORINVOKE
|
||||
ORPHANEDDIALOGTITLE
|
||||
ORSCANS
|
||||
@@ -1420,6 +1424,8 @@ RAWPATH
|
||||
rbhid
|
||||
rclsid
|
||||
RCZOOMIT
|
||||
remotedesktop
|
||||
rdp
|
||||
RDW
|
||||
READMODE
|
||||
READOBJECTS
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail.
|
||||
-->
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4948" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.37" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.250907003" />
|
||||
|
||||
@@ -834,6 +834,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LanguageModelProvider", "sr
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.UI.ViewModels.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.UI.ViewModels.UnitTests\Microsoft.CmdPal.UI.ViewModels.UnitTests.csproj", "{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.RemoteDesktop", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.RemoteDesktop\Microsoft.CmdPal.Ext.RemoteDesktop.csproj", "{2B3FB837-23DE-629F-82C6-42304E7083C9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests.csproj", "{DB34808A-FF91-D06E-A426-AFB5A8BD583B}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@@ -3036,6 +3040,22 @@ Global
|
||||
{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|x64.ActiveCfg = Release|x64
|
||||
{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|x64.Build.0 = Release|x64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Debug|x64.Build.0 = Debug|x64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Release|x64.ActiveCfg = Release|x64
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Release|x64.Build.0 = Release|x64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Debug|x64.Build.0 = Debug|x64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Release|x64.ActiveCfg = Release|x64
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -3367,6 +3387,8 @@ Global
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{45354F4F-1414-45CE-B600-51CD1209FD19} = {1AFB6476-670D-4E80-A464-657E01DFF482}
|
||||
{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{2B3FB837-23DE-629F-82C6-42304E7083C9} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
|
||||
{DB34808A-FF91-D06E-A426-AFB5A8BD583B} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
||||
@@ -36,6 +36,7 @@ public partial class MainListPage : DynamicListPage,
|
||||
"com.microsoft.cmdpal.builtin.websearch",
|
||||
"com.microsoft.cmdpal.builtin.windowssettings",
|
||||
"com.microsoft.cmdpal.builtin.datetime",
|
||||
"com.microsoft.cmdpal.builtin.remotedesktop",
|
||||
];
|
||||
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
@@ -13,6 +13,7 @@ using Microsoft.CmdPal.Ext.Calc;
|
||||
using Microsoft.CmdPal.Ext.ClipboardHistory;
|
||||
using Microsoft.CmdPal.Ext.Indexer;
|
||||
using Microsoft.CmdPal.Ext.Registry;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop;
|
||||
using Microsoft.CmdPal.Ext.Shell;
|
||||
using Microsoft.CmdPal.Ext.System;
|
||||
using Microsoft.CmdPal.Ext.TimeDate;
|
||||
@@ -151,6 +152,7 @@ public partial class App : Application
|
||||
services.AddSingleton<ICommandProvider, BuiltInsCommandProvider>();
|
||||
services.AddSingleton<ICommandProvider, TimeDateCommandsProvider>();
|
||||
services.AddSingleton<ICommandProvider, SystemCommandExtensionProvider>();
|
||||
services.AddSingleton<ICommandProvider, RemoteDesktopCommandProvider>();
|
||||
|
||||
// Models
|
||||
services.AddSingleton<TopLevelCommandManager>();
|
||||
|
||||
@@ -118,6 +118,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.ClipboardHistory\Microsoft.CmdPal.Ext.ClipboardHistory.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.RemoteDesktop\Microsoft.CmdPal.Ext.RemoteDesktop.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.System\Microsoft.CmdPal.Ext.System.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.WebSearch\Microsoft.CmdPal.Ext.WebSearch.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.Indexer\Microsoft.CmdPal.Ext.Indexer.csproj" />
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class FallbackRemoteDesktopItemTests
|
||||
{
|
||||
private static readonly CompositeFormat OpenHostCompositeFormat = CompositeFormat.Parse(Resources.remotedesktop_open_host);
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateQuery_WhenMatchingConnectionExists_UsesConnectionName()
|
||||
{
|
||||
var connectionName = "my-rdp-server";
|
||||
|
||||
// Arrange
|
||||
var setup = CreateFallback(connectionName);
|
||||
var fallback = setup.Fallback;
|
||||
|
||||
// Act
|
||||
fallback.UpdateQuery("my-rdp-server");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(connectionName, fallback.Title);
|
||||
var expectedSubtitle = string.Format(CultureInfo.CurrentCulture, OpenHostCompositeFormat, connectionName);
|
||||
Assert.AreEqual(expectedSubtitle, fallback.Subtitle);
|
||||
|
||||
var command = fallback.Command as OpenRemoteDesktopCommand;
|
||||
Assert.IsNotNull(command);
|
||||
Assert.AreEqual(Resources.remotedesktop_command_connect, command.Name);
|
||||
Assert.AreEqual(connectionName, GetCommandHost(command));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateQuery_WhenQueryIsValidHostWithoutExistingConnection_UsesQuery()
|
||||
{
|
||||
// Arrange
|
||||
var setup = CreateFallback();
|
||||
var fallback = setup.Fallback;
|
||||
const string hostname = "test.corp";
|
||||
|
||||
// Act
|
||||
fallback.UpdateQuery(hostname);
|
||||
|
||||
// Assert
|
||||
var expectedTitle = string.Format(CultureInfo.CurrentCulture, OpenHostCompositeFormat, hostname);
|
||||
Assert.AreEqual(expectedTitle, fallback.Title);
|
||||
Assert.AreEqual(Resources.remotedesktop_title, fallback.Subtitle);
|
||||
|
||||
var command = fallback.Command as OpenRemoteDesktopCommand;
|
||||
Assert.IsNotNull(command);
|
||||
Assert.AreEqual(Resources.remotedesktop_command_connect, command.Name);
|
||||
Assert.AreEqual(hostname, GetCommandHost(command));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateQuery_WhenQueryIsWhitespace_ResetsCommand()
|
||||
{
|
||||
// Arrange
|
||||
var setup = CreateFallback("rdp-server-two");
|
||||
var fallback = setup.Fallback;
|
||||
|
||||
// Act
|
||||
fallback.UpdateQuery(" ");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(Resources.remotedesktop_command_open, fallback.Title);
|
||||
Assert.AreEqual(string.Empty, fallback.Subtitle);
|
||||
|
||||
var command = fallback.Command as OpenRemoteDesktopCommand;
|
||||
Assert.IsNotNull(command);
|
||||
Assert.AreEqual(Resources.remotedesktop_command_open, command.Name);
|
||||
Assert.AreEqual(string.Empty, GetCommandHost(command));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdateQuery_WhenQueryIsInvalidHost_ClearsCommand()
|
||||
{
|
||||
// Arrange
|
||||
var setup = CreateFallback("rdp-server-three");
|
||||
var fallback = setup.Fallback;
|
||||
|
||||
// Act
|
||||
fallback.UpdateQuery("not a valid host");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(Resources.remotedesktop_command_open, fallback.Title);
|
||||
Assert.AreEqual(string.Empty, fallback.Subtitle);
|
||||
|
||||
var command = fallback.Command as OpenRemoteDesktopCommand;
|
||||
Assert.IsNotNull(command);
|
||||
Assert.AreEqual(Resources.remotedesktop_command_open, command.Name);
|
||||
Assert.AreEqual(string.Empty, GetCommandHost(command));
|
||||
}
|
||||
|
||||
private static string GetCommandHost(OpenRemoteDesktopCommand command)
|
||||
{
|
||||
var field = typeof(OpenRemoteDesktopCommand).GetField("_rdpHost", BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
if (field is null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return field.GetValue(command) as string ?? string.Empty;
|
||||
}
|
||||
|
||||
private static (FallbackRemoteDesktopItem Fallback, IRdpConnectionManager Manager) CreateFallback(params string[] connectionNames)
|
||||
{
|
||||
var settingsManager = new MockSettingsManager(connectionNames);
|
||||
var connectionsManager = new MockRDPConnectionsManager(settingsManager);
|
||||
|
||||
var fallback = new FallbackRemoteDesktopItem(connectionsManager);
|
||||
|
||||
return (fallback, connectionsManager);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<!-- Look at Directory.Build.props in root for common stuff as well -->
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests</RootNamespace>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" />
|
||||
<PackageReference Include="MSTest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.RemoteDesktop\Microsoft.CmdPal.Ext.RemoteDesktop.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Settings;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
|
||||
|
||||
internal sealed class MockRDPConnectionsManager : IRdpConnectionManager
|
||||
{
|
||||
private readonly List<ConnectionListItem> _connections = new();
|
||||
|
||||
public IReadOnlyCollection<ConnectionListItem> Connections => _connections.AsReadOnly();
|
||||
|
||||
public MockRDPConnectionsManager(ISettingsInterface settingsManager)
|
||||
{
|
||||
_connections.AddRange(settingsManager.PredefinedConnections.Select(ConnectionHelpers.MapToResult));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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 Microsoft.CmdPal.Ext.RemoteDesktop.Settings;
|
||||
using ToolkitSettings = Microsoft.CommandPalette.Extensions.Toolkit.Settings;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
|
||||
|
||||
internal sealed class MockSettingsManager : ISettingsInterface
|
||||
{
|
||||
private readonly List<string> _connections;
|
||||
|
||||
public IReadOnlyCollection<string> PredefinedConnections => _connections;
|
||||
|
||||
public ToolkitSettings Settings { get; } = new();
|
||||
|
||||
public MockSettingsManager(params string[] predefinedConnections)
|
||||
{
|
||||
_connections = new(predefinedConnections);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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.Linq;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class RDPConnectionsManagerTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void Constructor_AddsOpenCommandItem()
|
||||
{
|
||||
// Act
|
||||
var manager = new RDPConnectionsManager(new MockSettingsManager(["test.local"]));
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(manager.Connections.Any(item => string.IsNullOrEmpty(item.ConnectionName)));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FindConnection_ReturnsExactMatch()
|
||||
{
|
||||
// Arrange
|
||||
var connectionName = "rdp-test";
|
||||
var connection = new ConnectionListItem(connectionName);
|
||||
|
||||
// Act
|
||||
var result = ConnectionHelpers.FindConnection(connectionName, new[] { connection });
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.AreEqual(connectionName, result.ConnectionName);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FindConnection_ReturnsNullForWhitespaceQuery()
|
||||
{
|
||||
// Arrange
|
||||
var connection = new ConnectionListItem("rdp-test");
|
||||
|
||||
// Act
|
||||
var result = ConnectionHelpers.FindConnection(" ", new[] { connection });
|
||||
|
||||
// Assert
|
||||
Assert.IsNull(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
// 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.Linq;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Pages;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class RemoteDesktopCommandProviderTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void ProviderHasCorrectId()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual("com.microsoft.cmdpal.builtin.remotedesktop", provider.Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProviderHasDisplayName()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(provider.DisplayName);
|
||||
Assert.IsTrue(provider.DisplayName.Length > 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ProviderHasIcon()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(provider.Icon);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TopLevelCommandsNotEmpty()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Act
|
||||
var commands = provider.TopLevelCommands();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(commands);
|
||||
Assert.IsTrue(commands.Length > 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FallbackCommandsNotEmpty()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Act
|
||||
var commands = provider.FallbackCommands();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(commands);
|
||||
Assert.IsTrue(commands.Length > 0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TopLevelCommandsContainListPageCommand()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Act
|
||||
var commands = provider.TopLevelCommands();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, commands.Length);
|
||||
Assert.IsInstanceOfType(commands.Single().Command, typeof(RemoteDesktopListPage));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void FallbackCommandsContainFallbackItem()
|
||||
{
|
||||
// Setup
|
||||
var provider = new RemoteDesktopCommandProvider();
|
||||
|
||||
// Act
|
||||
var commands = provider.FallbackCommands();
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, commands.Length);
|
||||
Assert.IsInstanceOfType(commands.Single(), typeof(FallbackRemoteDesktopItem));
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,21 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="3" y="3.55078" width="9.36537" height="9.36537" rx="0.720413" fill="url(#paint0_linear_2155_27162)"/>
|
||||
<rect x="1" y="2" width="13" height="9" rx="0.722222" fill="url(#paint1_linear_2155_27162)"/>
|
||||
<circle cx="11.4" cy="9.4" r="4.4" fill="url(#paint2_radial_2155_27162)"/>
|
||||
<path d="M13.8703 11.2497C13.964 11.3434 14.116 11.3434 14.2097 11.2497C14.3034 11.156 14.3034 11.004 14.2097 10.9103L12.4594 9.16L14.2097 7.40971C14.3034 7.31598 14.3034 7.16402 14.2097 7.07029C14.116 6.97657 13.964 6.97657 13.8703 7.07029L11.9503 8.9903C11.8566 9.08402 11.8566 9.23598 11.9503 9.32971L13.8703 11.2497ZM9.40971 8.0303C9.31598 7.93657 9.16402 7.93657 9.07029 8.0303C8.97657 8.12402 8.97657 8.27598 9.07029 8.36971L10.8206 10.12L9.07029 11.8703C8.97657 11.964 8.97657 12.116 9.07029 12.2097C9.16402 12.3034 9.31598 12.3034 9.40971 12.2097L11.3297 10.2897C11.4234 10.196 11.4234 10.044 11.3297 9.95031L9.40971 8.0303Z" fill="#666666" stroke="#666666" stroke-width="0.146667"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_2155_27162" x1="3.22298" y1="3.55078" x2="8.52487" y2="6.68847" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#246FB0"/>
|
||||
<stop offset="1" stop-color="#14518A"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_2155_27162" x1="1.15476" y1="1.66667" x2="14.867" y2="9.90553" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#86D6F9"/>
|
||||
<stop offset="1" stop-color="#1FA3E4"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint2_radial_2155_27162" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10.9111 6.22222) rotate(90) scale(7.57778)">
|
||||
<stop stop-color="#E7ECF1"/>
|
||||
<stop offset="0.84" stop-color="#D2D4D6"/>
|
||||
<stop offset="1" stop-color="#A9ABAC"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -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.RemoteDesktop.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
|
||||
internal sealed partial class ConnectionListItem : ListItem
|
||||
{
|
||||
public ConnectionListItem(string connectionName)
|
||||
{
|
||||
ConnectionName = connectionName;
|
||||
|
||||
if (string.IsNullOrEmpty(connectionName))
|
||||
{
|
||||
Title = Resources.remotedesktop_open_rdp;
|
||||
Subtitle = Resources.remotedesktop_subtitle;
|
||||
}
|
||||
else
|
||||
{
|
||||
Title = connectionName;
|
||||
CompositeFormat remoteDesktopOpenHostFormat = CompositeFormat.Parse(Resources.remotedesktop_open_host);
|
||||
Subtitle = string.Format(CultureInfo.CurrentCulture, remoteDesktopOpenHostFormat, connectionName);
|
||||
}
|
||||
|
||||
Icon = Icons.RDPIcon;
|
||||
Command = new OpenRemoteDesktopCommand(connectionName);
|
||||
}
|
||||
|
||||
public string ConnectionName { get; }
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
// 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 System.Text;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
|
||||
internal sealed partial class FallbackRemoteDesktopItem : FallbackCommandItem
|
||||
{
|
||||
private const string _id = "com.microsoft.cmdpal.builtin.remotedesktop.fallback";
|
||||
|
||||
private static readonly UriHostNameType[] ValidUriHostNameTypes = [
|
||||
UriHostNameType.IPv6,
|
||||
UriHostNameType.IPv4,
|
||||
UriHostNameType.Dns
|
||||
];
|
||||
|
||||
private static readonly CompositeFormat RemoteDesktopOpenHostFormat = CompositeFormat.Parse(Resources.remotedesktop_open_host);
|
||||
private readonly IRdpConnectionManager _rdpConnectionsManager;
|
||||
|
||||
public FallbackRemoteDesktopItem(IRdpConnectionManager rdpConnectionsManager)
|
||||
: base(new OpenRemoteDesktopCommand(string.Empty), Resources.remotedesktop_title)
|
||||
{
|
||||
_rdpConnectionsManager = rdpConnectionsManager;
|
||||
|
||||
Title = string.Empty;
|
||||
Subtitle = string.Empty;
|
||||
Icon = Icons.RDPIcon;
|
||||
}
|
||||
|
||||
public override void UpdateQuery(string query)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
Title = string.Empty;
|
||||
Subtitle = string.Empty;
|
||||
Command = new OpenRemoteDesktopCommand(string.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
var connections = _rdpConnectionsManager.Connections.Where(w => !string.IsNullOrWhiteSpace(w.ConnectionName));
|
||||
|
||||
var queryConnection = ConnectionHelpers.FindConnection(query, connections);
|
||||
|
||||
if (queryConnection is not null && !string.IsNullOrWhiteSpace(queryConnection.ConnectionName))
|
||||
{
|
||||
var connectionName = queryConnection.ConnectionName;
|
||||
|
||||
Command = new OpenRemoteDesktopCommand(connectionName);
|
||||
Title = connectionName;
|
||||
Subtitle = string.Format(CultureInfo.CurrentCulture, RemoteDesktopOpenHostFormat, connectionName);
|
||||
}
|
||||
else if (ValidUriHostNameTypes.Contains(Uri.CheckHostName(query)))
|
||||
{
|
||||
var connectionName = query.Trim();
|
||||
Command = new OpenRemoteDesktopCommand(connectionName);
|
||||
Title = string.Format(CultureInfo.CurrentCulture, RemoteDesktopOpenHostFormat, connectionName);
|
||||
Subtitle = Resources.remotedesktop_title;
|
||||
}
|
||||
else
|
||||
{
|
||||
Title = string.Empty;
|
||||
Subtitle = string.Empty;
|
||||
Command = new OpenRemoteDesktopCommand(string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.Diagnostics;
|
||||
using System.Text;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
|
||||
internal sealed partial class OpenRemoteDesktopCommand : BaseObservable, IInvokableCommand
|
||||
{
|
||||
private static readonly CompositeFormat ProcessErrorFormat =
|
||||
CompositeFormat.Parse(Resources.remotedesktop_log_mstsc_error);
|
||||
|
||||
private static readonly CompositeFormat InvalidHostnameFormat =
|
||||
CompositeFormat.Parse(Resources.remotedesktop_log_invalid_hostname);
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Id { get; } = "com.microsoft.cmdpal.builtin.remotedesktop.openrdp";
|
||||
|
||||
public IIconInfo Icon => Icons.RDPIcon;
|
||||
|
||||
private readonly string _rdpHost;
|
||||
|
||||
public OpenRemoteDesktopCommand(string rdpHost)
|
||||
{
|
||||
_rdpHost = rdpHost;
|
||||
|
||||
Name = string.IsNullOrWhiteSpace(_rdpHost) ?
|
||||
Resources.remotedesktop_command_open :
|
||||
Resources.remotedesktop_command_connect;
|
||||
}
|
||||
|
||||
public ICommandResult Invoke(object sender)
|
||||
{
|
||||
using var process = new Process();
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.WorkingDirectory = Environment.SpecialFolder.MyDocuments.ToString();
|
||||
process.StartInfo.FileName = "mstsc";
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(_rdpHost))
|
||||
{
|
||||
// validate that _rdpHost is a proper hostname or IP address
|
||||
if (Uri.CheckHostName(_rdpHost) == UriHostNameType.Unknown)
|
||||
{
|
||||
return CommandResult.ShowToast(new ToastArgs()
|
||||
{
|
||||
Message = string.Format(
|
||||
System.Globalization.CultureInfo.CurrentCulture,
|
||||
InvalidHostnameFormat,
|
||||
_rdpHost),
|
||||
Result = CommandResult.KeepOpen(),
|
||||
});
|
||||
}
|
||||
|
||||
process.StartInfo.Arguments = $"/v:{_rdpHost}";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return CommandResult.ShowToast(new ToastArgs()
|
||||
{
|
||||
Message = string.Format(
|
||||
System.Globalization.CultureInfo.CurrentCulture,
|
||||
ProcessErrorFormat,
|
||||
ex.Message),
|
||||
Result = CommandResult.KeepOpen(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
|
||||
|
||||
internal static class ConnectionHelpers
|
||||
{
|
||||
public static ConnectionListItem MapToResult(string item) => new(item);
|
||||
|
||||
public static ConnectionListItem? FindConnection(string query, IEnumerable<ConnectionListItem> connections)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var matchedConnection = ListHelpers.FilterList(
|
||||
connections,
|
||||
query,
|
||||
(s, i) => ListHelpers.ScoreListItem(s, i))
|
||||
.FirstOrDefault();
|
||||
return matchedConnection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// 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 Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
|
||||
|
||||
internal interface IRdpConnectionManager
|
||||
{
|
||||
IReadOnlyCollection<ConnectionListItem> Connections { get; }
|
||||
}
|
||||
@@ -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.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Settings;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
|
||||
|
||||
internal class RDPConnectionsManager : IRdpConnectionManager
|
||||
{
|
||||
private readonly ISettingsInterface _settingsManager;
|
||||
private readonly ConnectionListItem _openRdpCommandListItem = new(string.Empty);
|
||||
|
||||
private ReadOnlyCollection<ConnectionListItem> _connections = new(Array.Empty<ConnectionListItem>());
|
||||
|
||||
private const int MinutesToCache = 1;
|
||||
private DateTime? _connectionsLastLoaded;
|
||||
|
||||
public RDPConnectionsManager(ISettingsInterface settingsManager)
|
||||
{
|
||||
_settingsManager = settingsManager;
|
||||
_settingsManager.Settings.SettingsChanged += (s, e) =>
|
||||
{
|
||||
_connectionsLastLoaded = null;
|
||||
};
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<ConnectionListItem> Connections
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_connectionsLastLoaded.HasValue ||
|
||||
(DateTime.Now - _connectionsLastLoaded.Value).TotalMinutes >= MinutesToCache)
|
||||
{
|
||||
var registryConnections = GetRdpConnectionsFromRegistry();
|
||||
var predefinedConnections = GetPredefinedConnectionsFromSettings();
|
||||
_connectionsLastLoaded = DateTime.Now;
|
||||
|
||||
var newConnections = new List<ConnectionListItem>(registryConnections.Count + predefinedConnections.Count + 1);
|
||||
newConnections.AddRange(registryConnections);
|
||||
newConnections.AddRange(predefinedConnections);
|
||||
newConnections.Insert(0, _openRdpCommandListItem);
|
||||
|
||||
Interlocked.Exchange(ref _connections, new ReadOnlyCollection<ConnectionListItem>(newConnections));
|
||||
}
|
||||
|
||||
return _connections;
|
||||
}
|
||||
}
|
||||
|
||||
private List<ConnectionListItem> GetRdpConnectionsFromRegistry()
|
||||
{
|
||||
using var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Terminal Server Client\Default");
|
||||
|
||||
var validConnections = new List<ConnectionListItem>();
|
||||
|
||||
if (key is not null)
|
||||
{
|
||||
validConnections = key.GetValueNames()
|
||||
.Select(name => key.GetValue(name))
|
||||
.OfType<string>() // Keep only string values
|
||||
.Select(v => v.Trim()) // Normalize
|
||||
.Where(v => !string.IsNullOrWhiteSpace(v))
|
||||
.Distinct() // Remove dupes if any
|
||||
.Select(ConnectionHelpers.MapToResult)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return validConnections;
|
||||
}
|
||||
|
||||
private List<ConnectionListItem> GetPredefinedConnectionsFromSettings()
|
||||
{
|
||||
var validConnections = _settingsManager.PredefinedConnections
|
||||
.Select(s => s.Trim())
|
||||
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||||
.Select(ConnectionHelpers.MapToResult)
|
||||
.ToList();
|
||||
|
||||
return validConnections;
|
||||
}
|
||||
}
|
||||
@@ -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.RemoteDesktop;
|
||||
|
||||
internal static class Icons
|
||||
{
|
||||
internal static IconInfo RDPIcon { get; } = IconHelpers.FromRelativePath("Assets\\RemoteDesktop.svg");
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
<Import Project="..\Common.ExtDependencies.props" />
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.RemoteDesktop</RootNamespace>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
|
||||
<ProjectPriFileName>Microsoft.CmdPal.Ext.RemoteDesktop.pri</ProjectPriFileName>
|
||||
<nullable>enable</nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
|
||||
<!-- CmdPal Toolkit reference now included via Common.ExtDependencies.props -->
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Assets\" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Update="Assets\RemoteDesktop.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\RemoteDesktop.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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.Linq;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Pages;
|
||||
|
||||
internal sealed partial class RemoteDesktopListPage : ListPage
|
||||
{
|
||||
private readonly IRdpConnectionManager _rdpConnectionManager;
|
||||
|
||||
public RemoteDesktopListPage(IRdpConnectionManager rdpConnectionManager)
|
||||
{
|
||||
Icon = Icons.RDPIcon;
|
||||
Name = Resources.remotedesktop_title;
|
||||
Id = "com.microsoft.cmdpal.builtin.remotedesktop";
|
||||
|
||||
_rdpConnectionManager = rdpConnectionManager;
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems() => _rdpConnectionManager.Connections.ToArray();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests")]
|
||||
153
src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.RemoteDesktop/Properties/Resources.Designer.cs
generated
Normal file
153
src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.RemoteDesktop/Properties/Resources.Designer.cs
generated
Normal file
@@ -0,0 +1,153 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <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.RemoteDesktop.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.RemoteDesktop.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 Connect.
|
||||
/// </summary>
|
||||
public static string remotedesktop_command_connect {
|
||||
get {
|
||||
return ResourceManager.GetString("remotedesktop_command_connect", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open.
|
||||
/// </summary>
|
||||
public static string remotedesktop_command_open {
|
||||
get {
|
||||
return ResourceManager.GetString("remotedesktop_command_open", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The hostname '{0}' was invalid. Ensure you're using a valid hostname or IP address..
|
||||
/// </summary>
|
||||
public static string remotedesktop_log_invalid_hostname {
|
||||
get {
|
||||
return ResourceManager.GetString("remotedesktop_log_invalid_hostname", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unable to initialize Microsoft Terminal Service Client. Ensure it is enabled in Windows Settings.\r{0}.
|
||||
/// </summary>
|
||||
public static string remotedesktop_log_mstsc_error {
|
||||
get {
|
||||
return ResourceManager.GetString("remotedesktop_log_mstsc_error", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Connect to {0}.
|
||||
/// </summary>
|
||||
public static string remotedesktop_open_host {
|
||||
get {
|
||||
return ResourceManager.GetString("remotedesktop_open_host", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open Remote Desktop Client.
|
||||
/// </summary>
|
||||
public static string remotedesktop_open_rdp {
|
||||
get {
|
||||
return ResourceManager.GetString("remotedesktop_open_rdp", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to A list of connections to include in the query results by default.
|
||||
/// </summary>
|
||||
public static string remotedesktop_settings_predefined_connections_description {
|
||||
get {
|
||||
return ResourceManager.GetString("remotedesktop_settings_predefined_connections_description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Predefined connections.
|
||||
/// </summary>
|
||||
public static string remotedesktop_settings_predefined_connections_title {
|
||||
get {
|
||||
return ResourceManager.GetString("remotedesktop_settings_predefined_connections_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Establish Remote Desktop connections.
|
||||
/// </summary>
|
||||
public static string remotedesktop_subtitle {
|
||||
get {
|
||||
return ResourceManager.GetString("remotedesktop_subtitle", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Remote Desktop.
|
||||
/// </summary>
|
||||
public static string remotedesktop_title {
|
||||
get {
|
||||
return ResourceManager.GetString("remotedesktop_title", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
<?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="remotedesktop_title" xml:space="preserve">
|
||||
<value>Remote Desktop</value>
|
||||
</data>
|
||||
<data name="remotedesktop_subtitle" xml:space="preserve">
|
||||
<value>Establish Remote Desktop connections</value>
|
||||
</data>
|
||||
<data name="remotedesktop_command_open" xml:space="preserve">
|
||||
<value>Open</value>
|
||||
</data>
|
||||
<data name="remotedesktop_open_host" xml:space="preserve">
|
||||
<value>Connect to {0}</value>
|
||||
</data>
|
||||
<data name="remotedesktop_command_connect" xml:space="preserve">
|
||||
<value>Connect</value>
|
||||
</data>
|
||||
<data name="remotedesktop_open_rdp" xml:space="preserve">
|
||||
<value>Open Remote Desktop Client</value>
|
||||
</data>
|
||||
<data name="remotedesktop_settings_predefined_connections_title" xml:space="preserve">
|
||||
<value>Predefined connections</value>
|
||||
</data>
|
||||
<data name="remotedesktop_settings_predefined_connections_description" xml:space="preserve">
|
||||
<value>A list of connections to include in the query results by default</value>
|
||||
</data>
|
||||
<data name="remotedesktop_log_mstsc_error" xml:space="preserve">
|
||||
<value>Unable to initialize Microsoft Terminal Service Client. Ensure it is enabled in Windows Settings.\r{0}</value>
|
||||
</data>
|
||||
<data name="remotedesktop_log_invalid_hostname" xml:space="preserve">
|
||||
<value>The hostname '{0}' was invalid. Ensure you're using a valid hostname or IP address.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -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 Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Pages;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
|
||||
using Microsoft.CmdPal.Ext.RemoteDesktop.Settings;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop;
|
||||
|
||||
public partial class RemoteDesktopCommandProvider : CommandProvider
|
||||
{
|
||||
private readonly CommandItem listPageCommand;
|
||||
private readonly FallbackRemoteDesktopItem fallback;
|
||||
|
||||
public RemoteDesktopCommandProvider()
|
||||
{
|
||||
Id = "com.microsoft.cmdpal.builtin.remotedesktop";
|
||||
DisplayName = Resources.remotedesktop_title;
|
||||
Icon = Icons.RDPIcon;
|
||||
|
||||
var settingsManager = new SettingsManager();
|
||||
var rdpConnectionsManager = new RDPConnectionsManager(settingsManager);
|
||||
var listPage = new RemoteDesktopListPage(rdpConnectionsManager);
|
||||
|
||||
fallback = new FallbackRemoteDesktopItem(rdpConnectionsManager);
|
||||
|
||||
listPageCommand = new CommandItem(listPage)
|
||||
{
|
||||
Subtitle = Resources.remotedesktop_subtitle,
|
||||
Icon = Icons.RDPIcon,
|
||||
MoreCommands = [
|
||||
new CommandContextItem(settingsManager.Settings.SettingsPage),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands() => [listPageCommand];
|
||||
|
||||
public override IFallbackCommandItem[] FallbackCommands() => [fallback];
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using ToolkitSettings = Microsoft.CommandPalette.Extensions.Toolkit.Settings;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Settings;
|
||||
|
||||
internal interface ISettingsInterface
|
||||
{
|
||||
public IReadOnlyCollection<string> PredefinedConnections { get; }
|
||||
|
||||
public ToolkitSettings Settings { get; }
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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 Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Settings;
|
||||
|
||||
internal class SettingsManager : JsonSettingsManager, ISettingsInterface
|
||||
{
|
||||
// Line break character used in WinUI3 TextBox and TextBlock.
|
||||
private const char TEXTBOXNEWLINE = '\r';
|
||||
|
||||
private static readonly string _namespace = "com.microsoft.cmdpal.builtin.remotedesktop";
|
||||
|
||||
private static string Namespaced(string propertyName) => $"{_namespace}.{propertyName}";
|
||||
|
||||
private readonly TextSetting _predefinedConnections = new(
|
||||
Namespaced(nameof(PredefinedConnections)),
|
||||
Resources.remotedesktop_settings_predefined_connections_title,
|
||||
Resources.remotedesktop_settings_predefined_connections_description,
|
||||
string.Empty)
|
||||
{
|
||||
Multiline = true,
|
||||
Placeholder = $"server1.domain.com{TEXTBOXNEWLINE}server2.domain.com{TEXTBOXNEWLINE}192.168.1.1",
|
||||
};
|
||||
|
||||
public IReadOnlyCollection<string> PredefinedConnections => _predefinedConnections.Value?.Split(TEXTBOXNEWLINE).ToList() ?? [];
|
||||
|
||||
internal static string SettingsJsonPath()
|
||||
{
|
||||
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
return Path.Combine(directory, "settings.json");
|
||||
}
|
||||
|
||||
public SettingsManager()
|
||||
{
|
||||
FilePath = SettingsJsonPath();
|
||||
|
||||
Settings.Add(_predefinedConnections);
|
||||
|
||||
// Load settings from file upon initialization
|
||||
LoadSettings();
|
||||
|
||||
Settings.SettingsChanged += (s, a) => this.SaveSettings();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user