b6593216f2
This commit introduces localization for the "Open in Windows Terminal" menu item and differentiates it based on compile-time branding (rather than runtime detection!). @leonMSFT's tray icon pull request had the excellent idea to use the TerminalApp's resource compartment for auxiliary resources for projects that can't otherwise be localized the same way. Doing localization in the shell extension (or WindowsTerminal.exe) would require us to use MUIRCT and split the build process up to support mui files. That's a huge amount of work... but this is *not* a huge amount of work. Fixes #6112
248 lines
7.4 KiB
C++
248 lines
7.4 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 cmdline{ wil::str_printf<std::wstring>(LR"-("%s" -d "%s\.")-", GetWtExePath().c_str(), 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 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 GetWtExePath), 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;
|
|
}
|