5c93e4bf12
Sets the working directory of the terminal when invoked from the shell extension. This ensures that new tabs opened with a starting directory of `.` open in the directory that the terminal was invoked from.
Closes #8933
## Validation Steps Performed
Manually tested - default PowerShell profile set to use home directory, Windows PowerShell profile set to use current directory. Launched via the shell extension and the default profile opened in the explorer directory, as did a new Windows PowerShell tab.
(cherry picked from commit f152573058
)
249 lines
7.5 KiB
C++
249 lines
7.5 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "pch.h"
|
|
#include "OpenTerminalHere.h"
|
|
#include "../WinRTUtils/inc/WtExeUtils.h"
|
|
#include "../WinRTUtils/inc/LibraryResources.h"
|
|
|
|
#include <winrt/Windows.ApplicationModel.Resources.Core.h>
|
|
#include <ShlObj.h>
|
|
|
|
static constexpr std::wstring_view VerbName{ L"WindowsTerminalOpenHere" };
|
|
|
|
// This code is aggressively copied from
|
|
// https://github.com/microsoft/Windows-classic-samples/blob/master/Samples/
|
|
// Win7Samples/winui/shell/appshellintegration/ExplorerCommandVerb/ExplorerCommandVerb.cpp
|
|
|
|
// 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*/)
|
|
{
|
|
wil::unique_cotaskmem_string pszName;
|
|
|
|
if (psiItemArray == nullptr)
|
|
{
|
|
// get the current path from explorer.exe
|
|
const auto path = this->_GetPathFromExplorer();
|
|
|
|
// no go, unable to get a reasonable path
|
|
if (path.empty())
|
|
{
|
|
return S_FALSE;
|
|
}
|
|
pszName = wil::make_cotaskmem_string(path.c_str(), path.length());
|
|
}
|
|
else
|
|
{
|
|
DWORD count;
|
|
psiItemArray->GetCount(&count);
|
|
|
|
winrt::com_ptr<IShellItem> psi;
|
|
RETURN_IF_FAILED(psiItemArray->GetItemAt(0, psi.put()));
|
|
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:\"
|
|
auto path{ wil::str_printf<std::wstring>(LR"-(%s\.)-", pszName.get()) };
|
|
auto cmdline{ wil::str_printf<std::wstring>(LR"-("%s" -d "%s")-", GetWtExePath().c_str(), path.c_str()) };
|
|
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(
|
|
nullptr, // lpApplicationName
|
|
cmdline.data(),
|
|
nullptr, // lpProcessAttributes
|
|
nullptr, // lpThreadAttributes
|
|
false, // bInheritHandles
|
|
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
|
|
nullptr, // lpEnvironment
|
|
path.data(),
|
|
&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 auto resource =
|
|
#if defined(WT_BRANDING_RELEASE)
|
|
RS_(L"ShellExtension_OpenInTerminalMenuItem");
|
|
#elif defined(WT_BRANDING_PREVIEW)
|
|
RS_(L"ShellExtension_OpenInTerminalMenuItem_Preview");
|
|
#else
|
|
RS_(L"ShellExtension_OpenInTerminalMenuItem_Dev");
|
|
#endif
|
|
return SHStrDup(resource.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)
|
|
try
|
|
{
|
|
std::filesystem::path modulePath{ wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle()) };
|
|
modulePath.replace_filename(WindowsTerminalExe);
|
|
// WindowsTerminal.exe,-101 will be the first icon group in WT
|
|
// We're using WindowsTerminal here explicitly, and not wt (from _getExePath), because
|
|
// WindowsTerminal is the only one built with the right icons.
|
|
const auto resource{ modulePath.wstring() + L",-101" };
|
|
return SHStrDupW(resource.c_str(), ppszIcon);
|
|
}
|
|
CATCH_RETURN();
|
|
|
|
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;
|
|
}
|
|
|
|
std::wstring OpenTerminalHere::_GetPathFromExplorer() const
|
|
{
|
|
using namespace std;
|
|
using namespace winrt;
|
|
|
|
wstring path;
|
|
HRESULT hr = NOERROR;
|
|
|
|
auto hwnd = ::GetForegroundWindow();
|
|
if (hwnd == nullptr)
|
|
{
|
|
return path;
|
|
}
|
|
|
|
TCHAR szName[MAX_PATH] = { 0 };
|
|
::GetClassName(hwnd, szName, MAX_PATH);
|
|
if (0 == StrCmp(szName, L"WorkerW") ||
|
|
0 == StrCmp(szName, L"Progman"))
|
|
{
|
|
//special folder: desktop
|
|
hr = ::SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, SHGFP_TYPE_CURRENT, szName);
|
|
if (FAILED(hr))
|
|
{
|
|
return path;
|
|
}
|
|
|
|
path = szName;
|
|
return path;
|
|
}
|
|
|
|
if (0 != StrCmp(szName, L"CabinetWClass"))
|
|
{
|
|
return path;
|
|
}
|
|
|
|
com_ptr<IShellWindows> shell;
|
|
try
|
|
{
|
|
shell = create_instance<IShellWindows>(CLSID_ShellWindows, CLSCTX_ALL);
|
|
}
|
|
catch (...)
|
|
{
|
|
//look like try_create_instance is not available no more
|
|
}
|
|
|
|
if (shell == nullptr)
|
|
{
|
|
return path;
|
|
}
|
|
|
|
com_ptr<IDispatch> disp;
|
|
wil::unique_variant variant;
|
|
variant.vt = VT_I4;
|
|
|
|
com_ptr<IWebBrowserApp> browser;
|
|
// look for correct explorer window
|
|
for (variant.intVal = 0;
|
|
shell->Item(variant, disp.put()) == S_OK;
|
|
variant.intVal++)
|
|
{
|
|
com_ptr<IWebBrowserApp> tmp;
|
|
if (FAILED(disp->QueryInterface(tmp.put())))
|
|
{
|
|
disp = nullptr; // get rid of DEBUG non-nullptr warning
|
|
continue;
|
|
}
|
|
|
|
HWND tmpHWND = NULL;
|
|
hr = tmp->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&tmpHWND));
|
|
if (hwnd == tmpHWND)
|
|
{
|
|
browser = tmp;
|
|
disp = nullptr; // get rid of DEBUG non-nullptr warning
|
|
break; //found
|
|
}
|
|
|
|
disp = nullptr; // get rid of DEBUG non-nullptr warning
|
|
}
|
|
|
|
if (browser != nullptr)
|
|
{
|
|
wil::unique_bstr url;
|
|
hr = browser->get_LocationURL(&url);
|
|
if (FAILED(hr))
|
|
{
|
|
return path;
|
|
}
|
|
|
|
wstring sUrl(url.get(), SysStringLen(url.get()));
|
|
DWORD size = MAX_PATH;
|
|
hr = ::PathCreateFromUrl(sUrl.c_str(), szName, &size, NULL);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
path = szName;
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|