diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 2711033404e2..49ebf8b5215e 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -1017,6 +1017,7 @@ ITemplate ITEMSTATEICONCLICK ITest ith +ITerminal IThrottled IThumbnail ITrigger @@ -1066,6 +1067,7 @@ KBDLLHOOKSTRUCT kbm KERNELBASE KEYBDINPUT +keybindings keyboardeventhandlers keyboardmanager keyboardmanagercommon diff --git a/.pipelines/ci/templates/build-powertoys-steps.yml b/.pipelines/ci/templates/build-powertoys-steps.yml index fbd0f5e94fba..95f2112e2619 100644 --- a/.pipelines/ci/templates/build-powertoys-steps.yml +++ b/.pipelines/ci/templates/build-powertoys-steps.yml @@ -138,7 +138,8 @@ steps: **\Microsoft.PowerToys.Run.Plugin.Calculator.UnitTest.dll **\Microsoft.Plugin.Uri.UnitTests.dll **\Wox.Test.dll - **\Microsoft.PowerToys.Run.Plugin.System.UnitTests.dll + **\Microsoft.PowerToys.Run.Plugin.System.UnitTests.dll + **\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests.dll !**\obj\** # Native dlls diff --git a/.pipelines/pipeline.user.windows.yml b/.pipelines/pipeline.user.windows.yml index 1c6d88345548..955bcf101f48 100644 --- a/.pipelines/pipeline.user.windows.yml +++ b/.pipelines/pipeline.user.windows.yml @@ -158,6 +158,7 @@ build: - 'modules\launcher\Plugins\VSCodeWorkspaces\Community.PowerToys.Run.Plugin.VSCodeWorkspaces.dll' - 'modules\launcher\Plugins\Service\Microsoft.PowerToys.Run.Plugin.Service.dll' - 'modules\launcher\Plugins\System\Microsoft.PowerToys.Run.Plugin.System.dll' + - 'modules\launcher\Plugins\WindowsTerminal\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.dll' - 'modules\launcher\PowerLauncher.dll' - 'modules\launcher\PowerLauncher.exe' - 'modules\launcher\PowerLauncher.Telemetry.dll' diff --git a/PowerToys.sln b/PowerToys.sln index b17039b9fdac..4d0113baa7ad 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -153,6 +153,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher", "src\module {5043CECE-E6A7-4867-9CBE-02D27D83747A} = {5043CECE-E6A7-4867-9CBE-02D27D83747A} {F8B870EB-D5F5-45BA-9CF7-A5C459818820} = {F8B870EB-D5F5-45BA-9CF7-A5C459818820} {74F1B9ED-F59C-4FE7-B473-7B453E30837E} = {74F1B9ED-F59C-4FE7-B473-7B453E30837E} + {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85} = {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85} {4BABF3FE-3451-42FD-873F-3C332E18DCEF} = {4BABF3FE-3451-42FD-873F-3C332E18DCEF} EndProjectSection EndProject @@ -367,6 +368,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfThumbnailProvider", "src EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-PdfThumbnailProvider", "src\modules\previewpane\UnitTests-PdfThumbnailProvider\UnitTests-PdfThumbnailProvider.csproj", "{F40C3397-1834-4530-B2D9-8F8B8456BCDF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.WindowsTerminal", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.WindowsTerminal\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.csproj", "{A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests", "src\modules\launcher\Plugins\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests\Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests.csproj", "{4ED320BC-BA04-4D42-8D15-CBE62151F08B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -605,9 +610,7 @@ Global {47310AB4-9034-4BD1-8D8B-E88AD21A171B}.Release|x86.ActiveCfg = Release|x64 {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x64.ActiveCfg = Debug|x64 {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x64.Build.0 = Debug|x64 - {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x86.ActiveCfg = Debug|Win32 - {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x86.Build.0 = Debug|Win32 - {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x86.Deploy.0 = Debug|Win32 + {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Debug|x86.ActiveCfg = Debug|x64 {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Release|x64.ActiveCfg = Release|x64 {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Release|x64.Build.0 = Release|x64 {A7D5099E-F0FD-4BF3-8522-5A682759F915}.Release|x86.ActiveCfg = Release|Win32 @@ -965,6 +968,19 @@ Global {F40C3397-1834-4530-B2D9-8F8B8456BCDF}.Release|x64.ActiveCfg = Release|x64 {F40C3397-1834-4530-B2D9-8F8B8456BCDF}.Release|x64.Build.0 = Release|x64 {F40C3397-1834-4530-B2D9-8F8B8456BCDF}.Release|x86.ActiveCfg = Release|x64 + {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Debug|x64.ActiveCfg = Debug|x64 + {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Debug|x64.Build.0 = Debug|x64 + {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Debug|x86.ActiveCfg = Debug|x64 + {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Release|x64.ActiveCfg = Release|x64 + {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Release|x64.Build.0 = Release|x64 + {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85}.Release|x86.ActiveCfg = Release|x64 + {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Debug|x64.ActiveCfg = Debug|x64 + {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Debug|x64.Build.0 = Debug|x64 + {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Debug|x86.ActiveCfg = Debug|x64 + {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x64.ActiveCfg = Release|x64 + {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x64.Build.0 = Release|x64 + {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x86.ActiveCfg = Release|Any CPU + {4ED320BC-BA04-4D42-8D15-CBE62151F08B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1080,6 +1096,8 @@ Global {470FBAF9-E1F8-4F3E-8786-198A1C81C8A8} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {11491FD8-F921-48BF-880C-7FEA185B80A1} = {2F305555-C296-497E-AC20-5FA1B237996A} {F40C3397-1834-4530-B2D9-8F8B8456BCDF} = {2F305555-C296-497E-AC20-5FA1B237996A} + {A2D583F0-B70C-4462-B1F0-8E81AFB7BA85} = {4AFC9975-2456-4C70-94A4-84073C1CED93} + {4ED320BC-BA04-4D42-8D15-CBE62151F08B} = {4AFC9975-2456-4C70-94A4-84073C1CED93} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 40f13e744ec3..c955872f568d 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -322,6 +322,9 @@ + + + @@ -1035,7 +1038,7 @@ - + @@ -1132,6 +1135,9 @@ + + + @@ -1325,6 +1331,17 @@ + + + + + + + + + + + diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests.csproj b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests.csproj new file mode 100644 index 000000000000..8b0f150129cf --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests.csproj @@ -0,0 +1,46 @@ + + + + netcoreapp3.1 + x64 + true + false + + + + + + + + + + + GlobalSuppressions.cs + + + StyleCop.json + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + PreserveNewest + + + + diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests/TerminalHelperTests.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests/TerminalHelperTests.cs new file mode 100644 index 000000000000..f1badf08b77f --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests/TerminalHelperTests.cs @@ -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.IO; +using System.Text.Json; +using Microsoft.PowerToys.Run.Plugin.WindowsTerminal.Helpers; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests +{ + [TestClass] + public class TerminalHelperTests + { + [DataTestMethod] + [DataRow("Windows PowerShell", true, "--window 0 nt --profile \"Windows PowerShell\"")] + [DataRow("Windows PowerShell", false, "--profile \"Windows PowerShell\"")] + public void ArgumentsTest(string profile, bool openNewTab, string expectedArguments) + { + var arguments = TerminalHelper.GetArguments(profile, openNewTab); + Assert.AreEqual(arguments, expectedArguments); + } + + [TestMethod] + public void ParseSettingsTest() + { + var terminal = new TerminalPackage(string.Empty, new Version(), string.Empty, string.Empty, string.Empty); + + var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.json"); + var settings = File.ReadAllText(settingsPath); + var profiles = TerminalHelper.ParseSettings(terminal, settings); + + Assert.AreEqual(profiles.Count, 4); + } + + [DataTestMethod] + [DataRow( + @"{""guid"":""{61c54bbd-c2c6-5271-96e7-009a87ff44bf}"",""name"":""Windows PowerShell"",""commandline"":""powershell.exe"",""hidden"":true}", + "61c54bbd-c2c6-5271-96e7-009a87ff44bf", + "Windows PowerShell", + true)] + [DataRow( + @"{""name"":""Windows PowerShell"",""commandline"":""powershell.exe"",""hidden"":false}", + null, + "Windows PowerShell", + false)] + public void ParseProfilesTest(string json, string identifier, string name, bool hidden) + { + var profileElement = JsonDocument.Parse(json).RootElement; + var terminal = new TerminalPackage(string.Empty, new Version(), string.Empty, string.Empty, string.Empty); + var profile = TerminalHelper.ParseProfile(terminal, profileElement); + + var expectedIdentifier = identifier != null ? new Guid(identifier) : null as Guid?; + Assert.AreEqual(profile.Terminal, terminal); + Assert.AreEqual(profile.Identifier, expectedIdentifier); + Assert.AreEqual(profile.Name, name); + Assert.AreEqual(profile.Hidden, hidden); + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests/settings.json b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests/settings.json new file mode 100644 index 000000000000..e2bcaa8f7a6b --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.UnitTests/settings.json @@ -0,0 +1,85 @@ +// This file was initially generated by Windows Terminal Preview 1.11.2421.0 +// It should still be usable in newer versions, but newer versions might have additional +// settings, help text, or changes that you will not see unless you clear this file +// and let us generate a new one for you. + +// To view the default settings, hold "alt" while clicking on the "Settings" button. +// For documentation on these settings, see: https://aka.ms/terminal-documentation +{ + "$schema": "https://aka.ms/terminal-profiles-schema", + + "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", + + // You can add more global application settings here. + // To learn more about global settings, visit https://aka.ms/terminal-global-settings + + // If enabled, selections are automatically copied to your clipboard. + "copyOnSelect": false, + + // If enabled, formatted data is also copied to your clipboard + "copyFormatting": false, + + // A profile specifies a command to execute paired with information about how it should look and feel. + // Each one of them will appear in the 'New Tab' dropdown, + // and can be invoked from the commandline with `wt.exe -p xxx` + // To learn more about profiles, visit https://aka.ms/terminal-profile-settings + "profiles": + { + "defaults": + { + // Put settings here that you want to apply to all profiles. + }, + "list": + [ + { + // Make changes here to the powershell.exe profile. + "guid": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", + "name": "Windows PowerShell", + "commandline": "powershell.exe", + "hidden": false + }, + { + // Make changes here to the cmd.exe profile. + "guid": "{0caa0dad-35be-5f56-a8ff-afceeeaa6101}", + "name": "Command Prompt", + "commandline": "cmd.exe", + "hidden": false + }, + { + "guid": "{b453ae62-4e3d-5e58-b989-0a998ec441b8}", + "name": "Azure Cloud Shell", + "source": "Windows.Terminal.Azure" + }, + { + "guid": "{2ece5bfe-50ed-5f3a-ab87-5cd4baafed2b}", + "name": "Git Bash", + "source": "Git" + } + ] + }, + + // Add custom color schemes to this array. + // To learn more about color schemes, visit https://aka.ms/terminal-color-schemes + "schemes": [], + + // Add custom actions and keybindings to this array. + // To unbind a key combination from your defaults.json, set the command to "unbound". + // To learn more about actions and keybindings, visit https://aka.ms/terminal-keybindings + "actions": + [ + // Copy and paste are bound to Ctrl+Shift+C and Ctrl+Shift+V in your defaults.json. + // These two lines additionally bind them to Ctrl+C and Ctrl+V. + // To learn more about selection, visit https://aka.ms/terminal-selection + { "command": {"action": "copy", "singleLine": false }, "keys": "ctrl+c" }, + { "command": "paste", "keys": "ctrl+v" }, + + // Press Ctrl+Shift+F to open the search box + { "command": "find", "keys": "ctrl+shift+f" }, + + // Press Alt+Shift+D to open a new pane. + // - "split": "auto" makes this pane open in the direction that provides the most surface area. + // - "splitMode": "duplicate" makes the new pane use the focused pane's profile. + // To learn more about panes, visit https://aka.ms/terminal-panes + { "command": { "action": "splitPane", "split": "auto", "splitMode": "duplicate" }, "keys": "alt+shift+d" } + ] +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/ApplicationActivationManager.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/ApplicationActivationManager.cs new file mode 100644 index 000000000000..b4d922d2c91a --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/ApplicationActivationManager.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Microsoft.PowerToys.Run.Plugin.WindowsTerminal.Helpers +{ + // Application Activation Manager Class + [ComImport] + [Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")] + public class ApplicationActivationManager : IApplicationActivationManager + { + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/] + public extern IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + public extern IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId); + + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + public extern IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId); + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/IApplicationActivationManager.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/IApplicationActivationManager.cs new file mode 100644 index 000000000000..309edd01b1b7 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/IApplicationActivationManager.cs @@ -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 System; +using System.Runtime.InteropServices; + +namespace Microsoft.PowerToys.Run.Plugin.WindowsTerminal.Helpers +{ + // Reference : https://github.com/MicrosoftEdge/edge-launcher/blob/108e63df0b4cb5cd9d5e45aa7a264690851ec51d/MIcrosoftEdgeLauncherCsharp/Program.cs + [Flags] + public enum ActivateOptions + { + None = 0x00000000, + DesignMode = 0x00000001, + NoErrorUI = 0x00000002, + NoSplashScreen = 0x00000004, + } + + // ApplicationActivationManager + [ComImport] + [Guid("2e941141-7f97-4756-ba1d-9decde894a3d")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IApplicationActivationManager + { + IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId); + + IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId); + + IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId); + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/ITerminalQuery.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/ITerminalQuery.cs new file mode 100644 index 000000000000..4666e3bb8ec4 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/ITerminalQuery.cs @@ -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; + +namespace Microsoft.PowerToys.Run.Plugin.WindowsTerminal.Helpers +{ + public interface ITerminalQuery + { + IEnumerable GetProfiles(); + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/TerminalHelper.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/TerminalHelper.cs new file mode 100644 index 000000000000..a9bf437368c8 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/TerminalHelper.cs @@ -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; +using System.Collections.Generic; +using System.Text.Json; + +namespace Microsoft.PowerToys.Run.Plugin.WindowsTerminal.Helpers +{ + public static class TerminalHelper + { + /// + /// Return the arguments for launch Windows Terminal + /// + /// Name of the Terminal profile + /// Whether to launch the profile in a new tab + public static string GetArguments(string profileName, bool openNewTab) + { + return openNewTab ? $"--window 0 nt --profile \"{profileName}\"" : $"--profile \"{profileName}\""; + } + + /// + /// Return a list of profiles for the Windows Terminal + /// + /// Windows Terminal package + /// Content of the settings JSON file of the Terminal + public static List ParseSettings(TerminalPackage terminal, string settingsJson) + { + var profiles = new List(); + + var options = new JsonDocumentOptions + { + CommentHandling = JsonCommentHandling.Skip, + }; + + var json = JsonDocument.Parse(settingsJson, options); + + json.RootElement.TryGetProperty("profiles", out JsonElement profilesElement); + if (profilesElement.ValueKind != JsonValueKind.Object) + { + return profiles; + } + + profilesElement.TryGetProperty("list", out JsonElement profilesList); + if (profilesList.ValueKind != JsonValueKind.Array) + { + return profiles; + } + + foreach (var profile in profilesList.EnumerateArray()) + { + profiles.Add(ParseProfile(terminal, profile)); + } + + return profiles; + } + + /// + /// Return a profile for the Windows Terminal + /// + /// Windows Terminal package + /// Profile from the settings JSON file + public static TerminalProfile ParseProfile(TerminalPackage terminal, JsonElement profileElement) + { + profileElement.TryGetProperty("name", out JsonElement nameElement); + var name = nameElement.ValueKind == JsonValueKind.String ? nameElement.GetString() : null; + + profileElement.TryGetProperty("hidden", out JsonElement hiddenElement); + var hidden = (hiddenElement.ValueKind == JsonValueKind.False || hiddenElement.ValueKind == JsonValueKind.True) && hiddenElement.GetBoolean(); + + profileElement.TryGetProperty("guid", out JsonElement guidElement); + var guid = guidElement.ValueKind == JsonValueKind.String ? Guid.Parse(guidElement.GetString()) : null as Guid?; + + return new TerminalProfile(terminal, name, guid, hidden); + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/TerminalQuery.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/TerminalQuery.cs new file mode 100644 index 000000000000..4a6a26db3e50 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Helpers/TerminalQuery.cs @@ -0,0 +1,64 @@ +// 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.IO; +using System.Linq; +using System.Security.Principal; +using Windows.Management.Deployment; + +namespace Microsoft.PowerToys.Run.Plugin.WindowsTerminal.Helpers +{ + public class TerminalQuery : ITerminalQuery + { + private readonly PackageManager _packageManager; + + private static ReadOnlyCollection Packages => new List + { + "Microsoft.WindowsTerminal", + "Microsoft.WindowsTerminalPreview", + }.AsReadOnly(); + + private IEnumerable Terminals => GetTerminals(); + + public TerminalQuery() + { + _packageManager = new PackageManager(); + } + + public IEnumerable GetProfiles() + { + var profiles = new List(); + + foreach (var terminal in Terminals) + { + if (!File.Exists(terminal.SettingsPath)) + { + continue; + } + + var settingsJson = File.ReadAllText(terminal.SettingsPath); + profiles.AddRange(TerminalHelper.ParseSettings(terminal, settingsJson)); + } + + return profiles.OrderBy(p => p.Name); + } + + private IEnumerable GetTerminals() + { + var user = WindowsIdentity.GetCurrent().User; + var localAppDataPath = Environment.GetEnvironmentVariable("LOCALAPPDATA"); + + foreach (var p in _packageManager.FindPackagesForUser(user.Value).Where(p => Packages.Contains(p.Id.Name))) + { + var aumid = p.GetAppListEntries().Single().AppUserModelId; + var version = new Version(p.Id.Version.Major, p.Id.Version.Minor, p.Id.Version.Build, p.Id.Version.Revision); + var settingsPath = Path.Combine(localAppDataPath, "Packages", p.Id.FamilyName, "LocalState", "settings.json"); + yield return new TerminalPackage(aumid, version, p.DisplayName, settingsPath, p.Logo.LocalPath); + } + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Images/WindowsTerminal.dark.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Images/WindowsTerminal.dark.png new file mode 100644 index 000000000000..6a10d746414a Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Images/WindowsTerminal.dark.png differ diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Images/WindowsTerminal.light.png b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Images/WindowsTerminal.light.png new file mode 100644 index 000000000000..fd4a0d2aaca4 Binary files /dev/null and b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Images/WindowsTerminal.light.png differ diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/LocProject.json b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/LocProject.json new file mode 100644 index 000000000000..444b15a9dd8a --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/LocProject.json @@ -0,0 +1,14 @@ +{ + "Projects": [ + { + "LanguageSet": "Azure_Languages", + "LocItems": [ + { + "SourceFile": "src\\modules\\launcher\\Plugins\\Microsoft.PowerToys.Run.Plugin.WindowsTerminal\\Properties\\Resources.resx", + "CopyOption": "LangIDOnName", + "OutputPath": "src\\modules\\launcher\\Plugins\\Microsoft.PowerToys.Run.Plugin.WindowsTerminal\\Properties" + } + ] + } + ] +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Main.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Main.cs new file mode 100644 index 000000000000..9a50cd00f491 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Main.cs @@ -0,0 +1,201 @@ +// 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.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Microsoft.PowerToys.Run.Plugin.WindowsTerminal.Helpers; +using Microsoft.PowerToys.Run.Plugin.WindowsTerminal.Properties; +using Microsoft.PowerToys.Settings.UI.Library; +using Wox.Infrastructure; +using Wox.Plugin; +using Wox.Plugin.Logger; + +namespace Microsoft.PowerToys.Run.Plugin.WindowsTerminal +{ + public class Main : IPlugin, IContextMenu, IPluginI18n, ISettingProvider + { + private const string OpenNewTab = nameof(OpenNewTab); + private const string ShowHiddenProfiles = nameof(ShowHiddenProfiles); + private readonly ITerminalQuery _terminalQuery = new TerminalQuery(); + private PluginInitContext _context; + private bool _openNewTab; + private bool _showHiddenProfiles; + private Dictionary _logoCache = new Dictionary(); + + public string Name => Resources.plugin_name; + + public string Description => Resources.plugin_description; + + public IEnumerable AdditionalOptions => new List() + { + new PluginAdditionalOption() + { + Key = OpenNewTab, + DisplayLabel = Resources.open_new_tab, + Value = false, + }, + + new PluginAdditionalOption() + { + Key = ShowHiddenProfiles, + DisplayLabel = Resources.show_hidden_profiles, + Value = false, + }, + }; + + public void Init(PluginInitContext context) + { + _context = context; + } + + public List Query(Query query) + { + var search = query?.Search ?? string.Empty; + var profiles = _terminalQuery.GetProfiles(); + + var result = new List(); + + foreach (var profile in profiles) + { + if (profile.Hidden && !_showHiddenProfiles) + { + continue; + } + + // Action keyword only or search query match + if ((!string.IsNullOrWhiteSpace(query.ActionKeyword) && string.IsNullOrWhiteSpace(search)) || StringMatcher.FuzzySearch(search, profile.Name).Success) + { + result.Add(new Result + { + Title = profile.Name, + SubTitle = profile.Terminal.DisplayName, + Icon = () => GetLogo(profile.Terminal), + Action = _ => + { + Launch(profile.Terminal.AppUserModelId, profile.Name); + return true; + }, + ContextData = profile, + }); + } + } + + return result; + } + + public List LoadContextMenus(Result selectedResult) + { + if (!(selectedResult?.ContextData is TerminalProfile)) + { + return new List(); + } + + var result = new List(); + + if (selectedResult.ContextData is TerminalProfile profile) + { + result.Add(new ContextMenuResult + { + Title = Resources.run_as_administrator, + Glyph = "\xE7EF", + FontFamily = "Segoe MDL2 Assets", + AcceleratorKey = Key.Enter, + AcceleratorModifiers = ModifierKeys.Control | ModifierKeys.Shift, + Action = _ => + { + LaunchElevated(profile.Terminal.AppUserModelId, profile.Name); + return true; + }, + }); + } + + return result; + } + + public string GetTranslatedPluginTitle() + { + return Resources.plugin_name; + } + + public string GetTranslatedPluginDescription() + { + return Resources.plugin_description; + } + + private void Launch(string id, string profile) + { + var appManager = new ApplicationActivationManager(); + const ActivateOptions noFlags = ActivateOptions.None; + var queryArguments = TerminalHelper.GetArguments(profile, _openNewTab); + try + { + appManager.ActivateApplication(id, queryArguments, noFlags, out var unusedPid); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + var name = "Plugin: " + Resources.plugin_name; + var message = Resources.run_terminal_failed; + Log.Exception("Failed to open Windows Terminal", ex, GetType()); + _context.API.ShowMsg(name, message, string.Empty); + } + } + + private void LaunchElevated(string id, string profile) + { + try + { + string path = "shell:AppsFolder\\" + id; + Helper.OpenInShell(path, TerminalHelper.GetArguments(profile, _openNewTab), runAsAdmin: true); + } +#pragma warning disable CA1031 // Do not catch general exception types + catch (Exception ex) +#pragma warning restore CA1031 // Do not catch general exception types + { + var name = "Plugin: " + Resources.plugin_name; + var message = Resources.run_terminal_failed; + Log.Exception("Failed to open Windows Terminal", ex, GetType()); + _context.API.ShowMsg(name, message, string.Empty); + } + } + + public Control CreateSettingPanel() + { + throw new NotImplementedException(); + } + + public void UpdateSettings(PowerLauncherPluginSettings settings) + { + var openNewTab = false; + var showHiddenProfiles = false; + + if (settings != null && settings.AdditionalOptions != null) + { + openNewTab = settings.AdditionalOptions.FirstOrDefault(x => x.Key == OpenNewTab)?.Value ?? false; + showHiddenProfiles = settings.AdditionalOptions.FirstOrDefault(x => x.Key == ShowHiddenProfiles)?.Value ?? false; + } + + _openNewTab = openNewTab; + _showHiddenProfiles = showHiddenProfiles; + } + + private ImageSource GetLogo(TerminalPackage terminal) + { + var aumid = terminal.AppUserModelId; + + if (!_logoCache.ContainsKey(aumid)) + { + _logoCache.Add(aumid, terminal.GetLogo()); + } + + return _logoCache[aumid]; + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.csproj b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.csproj new file mode 100644 index 000000000000..215a99e24fb4 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Microsoft.PowerToys.Run.Plugin.WindowsTerminal.csproj @@ -0,0 +1,103 @@ + + + + + + netcoreapp3.1 + Microsoft.PowerToys.Run.Plugin.WindowsTerminal + Microsoft.PowerToys.Run.Plugin.WindowsTerminal + $(Version).0 + false + false + x64 + en-US + + + + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\WindowsTerminal\ + DEBUG;TRACE + false + full + true + 8.0 + x64 + MinimumRecommendedRules.ruleset + 4 + true + + + + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\WindowsTerminal\ + TRACE + true + pdbonly + 8.0 + x64 + MinimumRecommendedRules.ruleset + 4 + true + + + + + + + + + + + + + + PreserveNewest + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + GlobalSuppressions.cs + + + StyleCop.json + + + + + 1.1.118 + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + True + True + Resources.resx + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.Designer.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.Designer.cs new file mode 100644 index 000000000000..054ff6c02f13 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.Designer.cs @@ -0,0 +1,117 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.PowerToys.Run.Plugin.WindowsTerminal.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // 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", "16.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() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [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.PowerToys.Run.Plugin.WindowsTerminal.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Open profiles in a new tab. + /// + internal static string open_new_tab { + get { + return ResourceManager.GetString("open_new_tab", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Open Windows Terminal profiles.. + /// + internal static string plugin_description { + get { + return ResourceManager.GetString("plugin_description", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Windows Terminal. + /// + internal static string plugin_name { + get { + return ResourceManager.GetString("plugin_name", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Run as administrator (Ctrl+Shift+Enter). + /// + internal static string run_as_administrator { + get { + return ResourceManager.GetString("run_as_administrator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to open Windows Terminal. + /// + internal static string run_terminal_failed { + get { + return ResourceManager.GetString("run_terminal_failed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show hidden profiles. + /// + internal static string show_hidden_profiles { + get { + return ResourceManager.GetString("show_hidden_profiles", resourceCulture); + } + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.resx b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.resx new file mode 100644 index 000000000000..c134b127e16f --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/Properties/Resources.resx @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Open profiles in a new tab + + + Open Windows Terminal profiles. + + + Windows Terminal + + + Run as administrator (Ctrl+Shift+Enter) + + + Failed to open Windows Terminal + + + Show hidden profiles + + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/TerminalPackage.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/TerminalPackage.cs new file mode 100644 index 000000000000..97e4934aecfa --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/TerminalPackage.cs @@ -0,0 +1,56 @@ +// 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 System.Windows.Media.Imaging; +using Wox.Infrastructure.Image; + +namespace Microsoft.PowerToys.Run.Plugin.WindowsTerminal +{ + public class TerminalPackage + { + public string AppUserModelId { get; } + + public Version Version { get; } + + public string DisplayName { get; } + + public string SettingsPath { get; } + + public string LogoPath { get; } + + public TerminalPackage(string appUserModelId, Version version, string displayName, string settingsPath, string logoPath) + { + AppUserModelId = appUserModelId; + Version = version; + DisplayName = displayName; + SettingsPath = settingsPath; + LogoPath = logoPath; + } + + public BitmapImage GetLogo() + { + if (File.Exists(LogoPath)) + { + var memoryStream = new MemoryStream(); + using (var fileStream = File.OpenRead(LogoPath)) + { + fileStream.CopyTo(memoryStream); + memoryStream.Position = 0; + + var image = new BitmapImage(); + image.BeginInit(); + image.StreamSource = memoryStream; + image.EndInit(); + return image; + } + } + else + { + return new BitmapImage(new Uri(ImageLoader.ErrorIconPath)); + } + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/TerminalProfile.cs b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/TerminalProfile.cs new file mode 100644 index 000000000000..fd097fdc6376 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/TerminalProfile.cs @@ -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; + +namespace Microsoft.PowerToys.Run.Plugin.WindowsTerminal +{ + public class TerminalProfile + { + public TerminalPackage Terminal { get; } + + public string Name { get; } + + public Guid? Identifier { get; } + + public bool Hidden { get; } + + public TerminalProfile(TerminalPackage terminal, string name, Guid? identifier, bool hidden) + { + Terminal = terminal; + Name = name; + Identifier = identifier; + Hidden = hidden; + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/plugin.json b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/plugin.json new file mode 100644 index 000000000000..ea70b4a6527f --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.PowerToys.Run.Plugin.WindowsTerminal/plugin.json @@ -0,0 +1,14 @@ +{ + "ID": "F59BA85006B14389A72A0EA756695F1D", + "Disabled": true, + "ActionKeyword": "_", + "IsGlobal": true, + "Name": "Windows Terminal", + "Author": "davidegiacometti", + "Version": "1.0.0", + "Language": "csharp", + "Website": "https://aka.ms/powertoys", + "ExecuteFileName": "Microsoft.PowerToys.Run.Plugin.WindowsTerminal.dll", + "IcoPathDark": "Images\\WindowsTerminal.dark.png", + "IcoPathLight": "Images\\WindowsTerminal.light.png" +}