From 1fc09979698a2ed5de674630171cd63c4599ef74 Mon Sep 17 00:00:00 2001 From: Mike Griese Date: Thu, 28 May 2020 10:42:13 -0500 Subject: [PATCH] Add a context menu entry to "Open Windows Terminal here" (#6100) ## Summary of the Pull Request ![image](https://user-images.githubusercontent.com/18356694/82586680-94447680-9b5d-11ea-9cf1-a85d2b32db10.png) I went with the simple option - just open the Terminal with the default profile in the selected directory. I'd love to add another entry for "Open Terminal here with Profile...", but that's going to be follow-up work, once we sort out pulling the Terminal Settings into their own dll. ## References * I'm going to need to file a bunch of follow-ups on this one. - We should add another entry to let the user select which profile - We should add the icon - I've got to do it in `dllname.dll,1` format, which is annoying. - These strings should be localized. - Should this only appear on Shift+right click? Probably! However, I don't know how to do that. * [A Win7 Explorer Command Sample](https://github.com/microsoft/Windows-classic-samples/tree/master/Samples/Win7Samples/winui/shell/appshellintegration/ExplorerCommandVerb) which hasn't aged well * [cppwinrt tutorial](https://docs.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/author-coclasses) on using COM in cppwinrt * [This is PowerToys' manifest](https://github.com/microsoft/PowerToys/blob/d2a60c7287eb5667b5282a519c92b759664c9e30/installer/MSIX/appxmanifest.xml#L53-L65) and then [their implementation](https://github.com/microsoft/PowerToys/blob/d16ebba9e0f06e7a0d41d981aeb1fd0a78192dc0/src/modules/powerrename/dll/PowerRenameExt.cpp) which were both helpful * [This ](https://docs.microsoft.com/en-us/windows/apps/desktop/modernize/desktop-to-uwp-extensions#instructions) was the sample I followed for how to actually set up the manifest, with the added magic that [`desktop5` lets you specify "Directory"](https://docs.microsoft.com/en-us/uwp/schemas/appxpackage/uapmanifestschema/element-desktop5-itemtype) ## PR Checklist * [x] Closes #1060 * [x] I work here * [ ] Tests added/passed * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments This adds a COM class that implements `IExplorerCommand`, which is what lets us populate the context menu entry. We expose that type through a new DLL that is simply responsible for the shell extension, so that explorer doesn't need to load the entire Terminal just to populate that entry. The COM class is tied to the application through some new entries in the manifest. The Clsid values are IMPORTANT - they must match the UUID of the implementation type. However, the `Verb` in the manifest didn't seem important. --- .../actions/spell-check/dictionary/apis.txt | 7 + .../spell-check/dictionary/microsoft.txt | 1 + .github/actions/spell-check/expect/expect.txt | 2 + .github/actions/spell-check/expect/web.txt | 2 + .../actions/spell-check/patterns/patterns.txt | 2 +- OpenConsole.sln | 26 +++ .../CascadiaPackage/CascadiaPackage.wapproj | 2 + .../CascadiaPackage/Package-Dev.appxmanifest | 25 ++- .../CascadiaPackage/Package-Pre.appxmanifest | 21 +- .../CascadiaPackage/Package.appxmanifest | 21 +- .../ShellExtension/OpenTerminalHere.cpp | 211 ++++++++++++++++++ .../ShellExtension/OpenTerminalHere.h | 51 +++++ .../ShellExtension/PlaceholderType.cpp | 10 + src/cascadia/ShellExtension/PlaceholderType.h | 37 +++ .../ShellExtension/PlaceholderType.idl | 21 ++ .../WindowsTerminalShellExt.def | 4 + .../WindowsTerminalShellExt.vcxproj | 68 ++++++ src/cascadia/ShellExtension/dllmain.cpp | 33 +++ src/cascadia/ShellExtension/packages.config | 4 + src/cascadia/ShellExtension/pch.cpp | 4 + src/cascadia/ShellExtension/pch.h | 30 +++ src/host/ft_host/Host.FeatureTests.vcxproj | 9 +- 22 files changed, 580 insertions(+), 11 deletions(-) create mode 100644 src/cascadia/ShellExtension/OpenTerminalHere.cpp create mode 100644 src/cascadia/ShellExtension/OpenTerminalHere.h create mode 100644 src/cascadia/ShellExtension/PlaceholderType.cpp create mode 100644 src/cascadia/ShellExtension/PlaceholderType.h create mode 100644 src/cascadia/ShellExtension/PlaceholderType.idl create mode 100644 src/cascadia/ShellExtension/WindowsTerminalShellExt.def create mode 100644 src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj create mode 100644 src/cascadia/ShellExtension/dllmain.cpp create mode 100644 src/cascadia/ShellExtension/packages.config create mode 100644 src/cascadia/ShellExtension/pch.cpp create mode 100644 src/cascadia/ShellExtension/pch.h diff --git a/.github/actions/spell-check/dictionary/apis.txt b/.github/actions/spell-check/dictionary/apis.txt index d50ce266603..1bf739dcf9b 100644 --- a/.github/actions/spell-check/dictionary/apis.txt +++ b/.github/actions/spell-check/dictionary/apis.txt @@ -2,9 +2,15 @@ ACCEPTFILES ACCESSDENIED bitfield bitfields +CLASSNOTAVAILABLE +EXPCMDFLAGS +EXPCMDSTATE href IBox +IBind ICustom +IClass +IExplorer IMap IObject IStorage @@ -12,6 +18,7 @@ LCID NCHITTEST NCLBUTTONDBLCLK NCRBUTTONDBLCLK +NOAGGREGATION NOREDIRECTIONBITMAP oaidl ocidl diff --git a/.github/actions/spell-check/dictionary/microsoft.txt b/.github/actions/spell-check/dictionary/microsoft.txt index 50831af918c..62cd27b72cd 100644 --- a/.github/actions/spell-check/dictionary/microsoft.txt +++ b/.github/actions/spell-check/dictionary/microsoft.txt @@ -3,6 +3,7 @@ mfcribbon microsoft microsoftonline osgvsowi +powerrename powershell tdbuildteamid vcruntime diff --git a/.github/actions/spell-check/expect/expect.txt b/.github/actions/spell-check/expect/expect.txt index 84ddc0aba21..8334d6e8225 100644 --- a/.github/actions/spell-check/expect/expect.txt +++ b/.github/actions/spell-check/expect/expect.txt @@ -603,6 +603,7 @@ dllexport DLLGETVERSIONPROC dllimport dllinit +dllmain DLLVERSIONINFO DLOAD DLOOK @@ -2088,6 +2089,7 @@ SHIFTJIS Shl shlguid shlobj +shobjidl shlwapi SHORTPATH SHOWCURSOR diff --git a/.github/actions/spell-check/expect/web.txt b/.github/actions/spell-check/expect/web.txt index 7ec457b6949..61bdfa1f807 100644 --- a/.github/actions/spell-check/expect/web.txt +++ b/.github/actions/spell-check/expect/web.txt @@ -4,3 +4,5 @@ www ecma rapidtables WCAG +winui +appshellintegration diff --git a/.github/actions/spell-check/patterns/patterns.txt b/.github/actions/spell-check/patterns/patterns.txt index 765eef4c23c..cddd540bffe 100644 --- a/.github/actions/spell-check/patterns/patterns.txt +++ b/.github/actions/spell-check/patterns/patterns.txt @@ -1,4 +1,4 @@ -https://(?:(?:[-a-zA-Z0-9?&=]*\.|)microsoft\.com)/[-a-zA-Z0-9?&=_\/.]* +https://(?:(?:[-a-zA-Z0-9?&=]*\.|)microsoft\.com)/[-a-zA-Z0-9?&=_#\/.]* https://aka\.ms/[-a-zA-Z0-9?&=\/_]* https://www.w3.org/[-a-zA-Z0-9?&=\/_#]* https://(?:(?:www\.|)youtube\.com|youtu.be)/[-a-zA-Z0-9?&=]* diff --git a/OpenConsole.sln b/OpenConsole.sln index bb55346956c..560dc62f757 100644 --- a/OpenConsole.sln +++ b/OpenConsole.sln @@ -6,6 +6,9 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Terminal", "Terminal", "{59840756-302F-44DF-AA47-441A9D673202}" EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "CascadiaPackage", "src\cascadia\CascadiaPackage\CascadiaPackage.wapproj", "{CA5CAD1A-224A-4171-B13A-F16E576FDD12}" + ProjectSection(ProjectDependencies) = postProject + {F2ED628A-DB22-446F-A081-4CC845B51A2B} = {F2ED628A-DB22-446F-A081-4CC845B51A2B} + EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.EXE", "src\host\exe\Host.EXE.vcxproj", "{9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B}" ProjectSection(ProjectDependencies) = postProject @@ -85,6 +88,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host.Tests.Feature", "src\h {18D09A24-8240-42D6-8CB6-236EEE820263} = {18D09A24-8240-42D6-8CB6-236EEE820263} {FC802440-AD6A-4919-8F2C-7701F2B38D79} = {FC802440-AD6A-4919-8F2C-7701F2B38D79} {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} = {9CBD7DFA-1754-4A9D-93D7-857A9D17CB1B} + {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A} = {58A03BB2-DF5A-4B66-91A0-7EF3BA01269A} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalParser.UnitTests", "src\terminal\parser\ut_parser\Parser.UnitTests.vcxproj", "{12144E07-FE63-4D33-9231-748B8D8C3792}" @@ -188,6 +192,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalApp", "src\cascadia EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TerminalSettings", "src\cascadia\TerminalSettings\TerminalSettings.vcxproj", "{CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsTerminalShellExt", "src\cascadia\ShellExtension\WindowsTerminalShellExt.vcxproj", "{F2ED628A-DB22-446F-A081-4CC845B51A2B}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTests_TerminalCore", "src\cascadia\UnitTests_TerminalCore\UnitTests.vcxproj", "{2C2BEEF4-9333-4D05-B12A-1905CBF112F9}" ProjectSection(ProjectDependencies) = postProject {06EC74CB-9A12-429C-B551-8562EC954747} = {06EC74CB-9A12-429C-B551-8562EC954747} @@ -1108,6 +1114,25 @@ Global {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x64.Build.0 = Release|x64 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x86.ActiveCfg = Release|Win32 {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907}.Release|x86.Build.0 = Release|Win32 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|Any CPU.ActiveCfg = Release|Win32 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|ARM64.ActiveCfg = Release|ARM64 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|x64.ActiveCfg = Release|x64 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|x64.Build.0 = Release|x64 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.AuditMode|x86.ActiveCfg = Release|Win32 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|ARM64.Build.0 = Debug|ARM64 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|x64.ActiveCfg = Debug|x64 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|x64.Build.0 = Debug|x64 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|x86.ActiveCfg = Debug|Win32 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Debug|x86.Build.0 = Debug|Win32 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|Any CPU.ActiveCfg = Release|Win32 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|ARM64.ActiveCfg = Release|ARM64 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|ARM64.Build.0 = Release|ARM64 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|x64.ActiveCfg = Release|x64 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|x64.Build.0 = Release|x64 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|x86.ActiveCfg = Release|Win32 + {F2ED628A-DB22-446F-A081-4CC845B51A2B}.Release|x86.Build.0 = Release|Win32 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64 {2C2BEEF4-9333-4D05-B12A-1905CBF112F9}.AuditMode|x64.ActiveCfg = AuditMode|x64 @@ -1558,6 +1583,7 @@ Global {CA5CAD1A-1754-4A9D-93D7-857A9D17CB1B} = {59840756-302F-44DF-AA47-441A9D673202} {CA5CAD1A-44BD-4AC7-AC72-F16E576FDD12} = {59840756-302F-44DF-AA47-441A9D673202} {CA5CAD1A-D7EC-4107-B7C6-79CB77AE2907} = {59840756-302F-44DF-AA47-441A9D673202} + {F2ED628A-DB22-446F-A081-4CC845B51A2B} = {59840756-302F-44DF-AA47-441A9D673202} {2C2BEEF4-9333-4D05-B12A-1905CBF112F9} = {BDB237B6-1D1D-400F-84CC-40A58FA59C8E} {EF3E32A7-5FF6-42B4-B6E2-96CD7D033F00} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB} {16376381-CE22-42BE-B667-C6B35007008D} = {81C352DB-1818-45B7-A284-18E259F1CC87} diff --git a/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj b/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj index cfff16b6435..6fbd04d0d53 100644 --- a/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj +++ b/src/cascadia/CascadiaPackage/CascadiaPackage.wapproj @@ -64,7 +64,9 @@ + + diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest index a8ccfe6c73c..da2051a1711 100644 --- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest @@ -3,9 +3,13 @@ @@ -39,7 +43,7 @@ Square150x150Logo="Images\Square150x150Logo.png" Square44x44Logo="Images\Square44x44Logo.png"> @@ -58,6 +62,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest index c69242ccb1b..ba258abe77c 100644 --- a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest @@ -41,7 +41,7 @@ Square150x150Logo="Images\Square150x150Logo.png" Square44x44Logo="Images\Square44x44Logo.png"> @@ -60,6 +60,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest index ad429698b69..c7dad075204 100644 --- a/src/cascadia/CascadiaPackage/Package.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package.appxmanifest @@ -41,7 +41,7 @@ Square150x150Logo="Images\Square150x150Logo.png" Square44x44Logo="Images\Square44x44Logo.png"> @@ -60,6 +60,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/cascadia/ShellExtension/OpenTerminalHere.cpp b/src/cascadia/ShellExtension/OpenTerminalHere.cpp new file mode 100644 index 00000000000..8091754832c --- /dev/null +++ b/src/cascadia/ShellExtension/OpenTerminalHere.cpp @@ -0,0 +1,211 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "OpenTerminalHere.h" + +// TODO GH#6112: Localize these strings +static constexpr std::wstring_view VerbDisplayName{ L"Open in Windows Terminal" }; +static constexpr std::wstring_view VerbDevBuildDisplayName{ L"Open in Windows Terminal (Dev Build)" }; +static constexpr std::wstring_view VerbName{ L"WindowsTerminalOpenHere" }; + +static constexpr std::wstring_view WtExe{ L"wt.exe" }; +static constexpr std::wstring_view WtdExe{ L"wtd.exe" }; +static constexpr std::wstring_view WindowsTerminalExe{ L"WindowsTerminal.exe" }; + +static constexpr std::wstring_view LocalAppDataAppsPath{ L"%LOCALAPPDATA%\\Microsoft\\WindowsApps\\" }; + +// This code is aggressively copied from +// https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/ +// Win7Samples/winui/shell/appshellintegration/ExplorerCommandVerb/ExplorerCommandVerb.cpp + +// Function Description: +// - This is a helper to determine if we're running as a part of the Dev Build +// Package or the release package. We'll need to return different text, icons, +// and use different commandlines depending on which one the user requested. +// - Uses a C++11 "magic static" to make sure this is only computed once. +// - If we can't determine if it's the dev build or not, we'll default to true +// Arguments: +// - +// Return Value: +// - true if we believe this extension is being run in the dev build package. +static bool IsDevBuild() +{ + // use C++11 magic statics to make sure we only do this once. + static bool isDevBuild = []() -> bool { + try + { + const auto package{ winrt::Windows::ApplicationModel::Package::Current() }; + const auto id = package.Id(); + const std::wstring name{ id.FullName() }; + // Does our PFN start with WindowsTerminalDev? + return name.rfind(L"WindowsTerminalDev", 0) == 0; + } + CATCH_LOG(); + return true; + }(); + + return isDevBuild; +} + +// Function Description: +// - Helper function for getting the path to the appropriate executable to use +// for this instance of the shell extension. If we're running the dev build, +// it should be a `wtd.exe`, but if we're preview or release, we want to make +// sure to get the correct `wt.exe` that corresponds to _us_. +// - If we're unpackaged, this needs to get us `WindowsTerminal.exe`, because +// the `wt*exe` alias won't have been installed for this install. +// Arguments: +// - +// Return Value: +// - the full path to the exe, one of `wt.exe`, `wtd.exe`, or `WindowsTerminal.exe`. +static std::wstring _getExePath() +{ + // use C++11 magic statics to make sure we only do this once. + static const std::wstring exePath = []() -> std::wstring { + // First, check a packaged location for the exe. If we've got a package + // family name, that means we're one of the packaged Dev build, packaged + // Release build, or packaged Preview build. + // + // If we're the preview or release build, there's no way of knowing if the + // `wt.exe` on the %PATH% is us or not. Fortunately, _our_ execution alias + // is located in "%LOCALAPPDATA%\Microsoft\WindowsApps\", _always_, so we can use that to look up the exe easier. + try + { + const auto package{ winrt::Windows::ApplicationModel::Package::Current() }; + const auto id = package.Id(); + const std::wstring pfn{ id.FamilyName() }; + if (!pfn.empty()) + { + const std::filesystem::path windowsAppsPath{ wil::ExpandEnvironmentStringsW(LocalAppDataAppsPath.data()) }; + const std::filesystem::path wtPath = windowsAppsPath / pfn / (IsDevBuild() ? WtdExe : WtExe); + return wtPath; + } + } + CATCH_LOG(); + + // If we're here, then we couldn't resolve our exe from the package. This + // means we're running unpackaged. We should just use the + // WindowsTerminal.exe that's sitting in the directory next to us. + try + { + HMODULE hModule = GetModuleHandle(nullptr); + THROW_LAST_ERROR_IF(hModule == nullptr); + std::wstring dllPathString; + THROW_IF_FAILED(wil::GetModuleFileNameW(hModule, dllPathString)); + const std::filesystem::path dllPath{ dllPathString }; + const std::filesystem::path rootDir = dllPath.parent_path(); + std::filesystem::path wtPath = rootDir / WindowsTerminalExe; + return wtPath; + } + CATCH_LOG(); + + return L"wt.exe"; + }(); + return exePath; +} + +// Method Description: +// - This method is called when the user activates the context menu item. We'll +// launch the Terminal using the current working directory. +// Arguments: +// - psiItemArray: a IShellItemArray which contains the item that's selected. +// Return Value: +// - S_OK if we successfully attempted to launch the Terminal, otherwise a +// failure from an earlier HRESULT. +HRESULT OpenTerminalHere::Invoke(IShellItemArray* psiItemArray, + IBindCtx* /*pBindContext*/) +{ + DWORD count; + psiItemArray->GetCount(&count); + + winrt::com_ptr psi; + RETURN_IF_FAILED(psiItemArray->GetItemAt(0, psi.put())); + + wil::unique_cotaskmem_string pszName; + RETURN_IF_FAILED(psi->GetDisplayName(SIGDN_FILESYSPATH, &pszName)); + + { + wil::unique_process_information _piClient; + STARTUPINFOEX siEx{ 0 }; + siEx.StartupInfo.cb = sizeof(STARTUPINFOEX); + + // Append a "\." to the given path, so that this will work in "C:\" + std::wstring cmdline = fmt::format(L"\"{}\" -d \"{}\\.\"", _getExePath(), pszName.get()); + RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW( + nullptr, + cmdline.data(), + nullptr, // lpProcessAttributes + nullptr, // lpThreadAttributes + false, // bInheritHandles + EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags + nullptr, // lpEnvironment + nullptr, + &siEx.StartupInfo, // lpStartupInfo + &_piClient // lpProcessInformation + )); + } + + return S_OK; +} + +HRESULT OpenTerminalHere::GetToolTip(IShellItemArray* /*psiItemArray*/, + LPWSTR* ppszInfoTip) +{ + // tooltip provided here, in this case none is provided + *ppszInfoTip = nullptr; + return E_NOTIMPL; +} + +HRESULT OpenTerminalHere::GetTitle(IShellItemArray* /*psiItemArray*/, + LPWSTR* ppszName) +{ + // Change the string we return depending on if we're running from the dev + // build package or not. + const bool isDevBuild = IsDevBuild(); + return SHStrDup(isDevBuild ? VerbDevBuildDisplayName.data() : VerbDisplayName.data(), ppszName); +} + +HRESULT OpenTerminalHere::GetState(IShellItemArray* /*psiItemArray*/, + BOOL /*fOkToBeSlow*/, + EXPCMDSTATE* pCmdState) +{ + // compute the visibility of the verb here, respect "fOkToBeSlow" if this is + // slow (does IO for example) when called with fOkToBeSlow == FALSE return + // E_PENDING and this object will be called back on a background thread with + // fOkToBeSlow == TRUE + + // We however don't need to bother with any of that, so we'll just return + // ECS_ENABLED. + + *pCmdState = ECS_ENABLED; + return S_OK; +} + +HRESULT OpenTerminalHere::GetIcon(IShellItemArray* /*psiItemArray*/, + LPWSTR* ppszIcon) +{ + // the icon ref ("dll,-") is provided here, in this case none is provided + *ppszIcon = nullptr; + // TODO GH#6111: Return the Terminal icon here + return E_NOTIMPL; +} + +HRESULT OpenTerminalHere::GetFlags(EXPCMDFLAGS* pFlags) +{ + *pFlags = ECF_DEFAULT; + return S_OK; +} + +HRESULT OpenTerminalHere::GetCanonicalName(GUID* pguidCommandName) +{ + *pguidCommandName = __uuidof(this); + return S_OK; +} + +HRESULT OpenTerminalHere::EnumSubCommands(IEnumExplorerCommand** ppEnum) +{ + *ppEnum = nullptr; + return E_NOTIMPL; +} diff --git a/src/cascadia/ShellExtension/OpenTerminalHere.h b/src/cascadia/ShellExtension/OpenTerminalHere.h new file mode 100644 index 00000000000..5be0c0d370c --- /dev/null +++ b/src/cascadia/ShellExtension/OpenTerminalHere.h @@ -0,0 +1,51 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- OpenTerminalHere.h + +Abstract: +- This is the class that implements our Explorer Context Menu item. By + implementing IExplorerCommand, we can provide an entry to the context menu to + allow the user to open the Terminal in the current working directory. +- Importantly, we need to make sure to declare the GUID of this implementation + class explicitly, so we can refer to it in our manifest, and use it to create + instances of this class when the shell asks for one. +- This is defined as a WRL type, so that we can use WRL's CoCreatableClass magic + to create the class factory and module management for us. See more details in + dllmain.cpp. + +Author(s): +- Mike Griese - May 2020 + +--*/ +#pragma once + +#include +#include "../../cascadia/inc/cppwinrt_utils.h" + +using namespace Microsoft::WRL; + +struct __declspec(uuid("9f156763-7844-4dc4-b2b1-901f640f5155")) + OpenTerminalHere : public RuntimeClass, IExplorerCommand> +{ +#pragma region IExplorerCommand + HRESULT Invoke(IShellItemArray* psiItemArray, + IBindCtx* pBindContext); + HRESULT GetToolTip(IShellItemArray* psiItemArray, + LPWSTR* ppszInfoTip); + HRESULT GetTitle(IShellItemArray* psiItemArray, + LPWSTR* ppszName); + HRESULT GetState(IShellItemArray* psiItemArray, + BOOL fOkToBeSlow, + EXPCMDSTATE* pCmdState); + HRESULT GetIcon(IShellItemArray* psiItemArray, + LPWSTR* ppszIcon); + HRESULT GetFlags(EXPCMDFLAGS* pFlags); + HRESULT GetCanonicalName(GUID* pguidCommandName); + HRESULT EnumSubCommands(IEnumExplorerCommand** ppEnum); +#pragma endregion +}; + +CoCreatableClass(OpenTerminalHere); diff --git a/src/cascadia/ShellExtension/PlaceholderType.cpp b/src/cascadia/ShellExtension/PlaceholderType.cpp new file mode 100644 index 00000000000..dc45a6a1e47 --- /dev/null +++ b/src/cascadia/ShellExtension/PlaceholderType.cpp @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "PlaceholderType.h" +#include "PlaceholderType.g.cpp" + +namespace winrt::Microsoft::Terminal::ShellExtension::implementation +{ +} diff --git a/src/cascadia/ShellExtension/PlaceholderType.h b/src/cascadia/ShellExtension/PlaceholderType.h new file mode 100644 index 00000000000..d56395c9257 --- /dev/null +++ b/src/cascadia/ShellExtension/PlaceholderType.h @@ -0,0 +1,37 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- PlaceholderType.h + +Abstract: +- This class is just here to make our .wapproj play nicely with this project. If + we don't define any winrt types, then we won't generate a .winmd, and the + .wapproj will become _very_ mad at this project. So we'll use this placeholder + class just to trick cppwinrt into generating a winmd for us. If we ever _do_ + add a real winrt type to this project, this can be removed. + +Author(s): +- Mike Griese - May 2020 + +--*/ +#pragma once + +#include +#include "PlaceholderType.g.h" +#include "../../cascadia/inc/cppwinrt_utils.h" + +namespace winrt::Microsoft::Terminal::ShellExtension::implementation +{ + struct PlaceholderType : PlaceholderTypeT + { + PlaceholderType() = default; + GETSET_PROPERTY(int32_t, Placeholder, 42); + }; +} + +namespace winrt::Microsoft::Terminal::ShellExtension::factory_implementation +{ + BASIC_FACTORY(PlaceholderType); +} diff --git a/src/cascadia/ShellExtension/PlaceholderType.idl b/src/cascadia/ShellExtension/PlaceholderType.idl new file mode 100644 index 00000000000..0dbe7adfacc --- /dev/null +++ b/src/cascadia/ShellExtension/PlaceholderType.idl @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// This class is just here to make our .wapproj play nicely with this project. +// If we don't define any winrt types, then we won't generate a .winmd, and the +// .wapproj will become _very_ mad at this project. So we'll use this +// placeholder class just to trick cppwinrt into generating a winmd for us. If +// we ever _do_ add a real winrt type to this project, this can be removed. + +namespace Microsoft.Terminal.ShellExtension +{ + [default_interface] runtimeclass PlaceholderType { + PlaceholderType(); + Int32 Placeholder + { + get; + set; + }; + }; + +} diff --git a/src/cascadia/ShellExtension/WindowsTerminalShellExt.def b/src/cascadia/ShellExtension/WindowsTerminalShellExt.def new file mode 100644 index 00000000000..d0e5f0b59d3 --- /dev/null +++ b/src/cascadia/ShellExtension/WindowsTerminalShellExt.def @@ -0,0 +1,4 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE +DllGetClassObject PRIVATE diff --git a/src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj b/src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj new file mode 100644 index 00000000000..12565176959 --- /dev/null +++ b/src/cascadia/ShellExtension/WindowsTerminalShellExt.vcxproj @@ -0,0 +1,68 @@ + + + + {f2ed628a-db22-446f-a081-4cc845b51a2b} + WindowsTerminalShellExt + Microsoft.Terminal.ShellExtension + + + DynamicLibrary + Console + + true + + true + + + + + + + + + + PlaceholderType.idl + + + + + Create + + + PlaceholderType.idl + + + + + + + + + + + + + + + + + + {18D09A24-8240-42D6-8CB6-236EEE820263} + + + {CA5CAD1A-039A-4929-BA2A-8BEB2E4106FE} + false + + + + + + diff --git a/src/cascadia/ShellExtension/dllmain.cpp b/src/cascadia/ShellExtension/dllmain.cpp new file mode 100644 index 00000000000..d7c93e96f31 --- /dev/null +++ b/src/cascadia/ShellExtension/dllmain.cpp @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "OpenTerminalHere.h" + +// For reference, see: +// * https://docs.microsoft.com/en-us/cpp/cppcx/wrl/how-to-create-a-classic-com-component-using-wrl?view=vs-2019 +// * https://docs.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/move-to-winrt-from-wrl#porting-a-wrl-module-microsoftwrlmodule +// +// We don't need to implement DllGetActivationFactory or DllCanUnloadNow +// manually, since the generated module.g.cpp will handle it for us, and will +// handle our WRL types appropriately. +// +// We DO need to implement DllGetClassObject, because that's what explorer.exe +// will call to attempt to create a class factory for our shell extension. The +// CoCreatableClass macro in OpenTerminalHere.h will create the factory for us, +// so that the GetClassObject call will work like magic. + +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, _COM_Outptr_ void** ppv) +{ + return Microsoft::WRL::Module::GetModule().GetClassObject(rclsid, riid, ppv); +} + +STDAPI_(BOOL) +DllMain(_In_opt_ HINSTANCE hinst, DWORD reason, _In_opt_ void*) +{ + if (reason == DLL_PROCESS_ATTACH) + { + DisableThreadLibraryCalls(hinst); + } + return TRUE; +} diff --git a/src/cascadia/ShellExtension/packages.config b/src/cascadia/ShellExtension/packages.config new file mode 100644 index 00000000000..8db4233e6a9 --- /dev/null +++ b/src/cascadia/ShellExtension/packages.config @@ -0,0 +1,4 @@ + + + + diff --git a/src/cascadia/ShellExtension/pch.cpp b/src/cascadia/ShellExtension/pch.cpp new file mode 100644 index 00000000000..3c27d44d570 --- /dev/null +++ b/src/cascadia/ShellExtension/pch.cpp @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" diff --git a/src/cascadia/ShellExtension/pch.h b/src/cascadia/ShellExtension/pch.h new file mode 100644 index 00000000000..5962c68100a --- /dev/null +++ b/src/cascadia/ShellExtension/pch.h @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// +// pch.h +// Header for platform projection include files +// + +#pragma once + +#define WIN32_LEAN_AND_MEAN + +#include +// This is inexplicable, but for whatever reason, cppwinrt conflicts with the +// SDK definition of this function, so the only fix is to undef it. +// from WinBase.h +// Windows::UI::Xaml::Media::Animation::IStoryboard::GetCurrentTime +#ifdef GetCurrentTime +#undef GetCurrentTime +#endif + +#include +#include +#include +#include + +#include +#include + +#include +#include diff --git a/src/host/ft_host/Host.FeatureTests.vcxproj b/src/host/ft_host/Host.FeatureTests.vcxproj index b31c4ef4490..e62dc672862 100644 --- a/src/host/ft_host/Host.FeatureTests.vcxproj +++ b/src/host/ft_host/Host.FeatureTests.vcxproj @@ -44,7 +44,7 @@ {18d09a24-8240-42d6-8cb6-236eee820263} - + {58a03bb2-df5a-4b66-91a0-7ef3ba01269a} @@ -59,9 +59,4 @@ - - - $(OutDir)\conpty.lib;%(AdditionalDependencies) - - - \ No newline at end of file +