From 87d96ea7319f4271286bbe4e1aa5e136d7bc9419 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Tue, 16 Sep 2025 12:55:42 -0500 Subject: [PATCH] HAX: Expose Terminal's commands to PowerToys Command Palette --- .../CascadiaPackage/Package-Dev.appxmanifest | 20 ++ .../TerminalApp/AppActionHandlers.cpp | 5 + src/cascadia/TerminalApp/AppLogic.cpp | 250 ++++++++++++++++++ .../TerminalApp/TerminalAppLib.vcxproj | 6 + src/cascadia/TerminalApp/TerminalPage.h | 2 + 5 files changed, 283 insertions(+) diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest index 4a3153076c..f7cfe3685f 100644 --- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest @@ -122,6 +122,7 @@ + @@ -139,6 +140,25 @@ + + + + + + + + + + + + + + + diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 9edcb6a81c..dee4f6d3d5 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -31,6 +31,11 @@ namespace winrt namespace winrt::TerminalApp::implementation { + void TerminalPage::DispatchAction(const Settings::Model::ActionAndArgs& args) + { + _actionDispatch->DoAction(args); + } + TermControl TerminalPage::_senderOrActiveControl(const IInspectable& sender) { if (sender) diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 0563adbd7a..fbfcdb8d78 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -12,6 +12,11 @@ #include "../../types/inc/utils.hpp" +#include +#include + +#include "fzf/fzf.h" + using namespace winrt::Windows::ApplicationModel; using namespace winrt::Windows::ApplicationModel::DataTransfer; using namespace winrt::Windows::UI::Xaml; @@ -26,9 +31,246 @@ using namespace ::TerminalApp; namespace winrt { namespace MUX = Microsoft::UI::Xaml; + namespace MCP = Microsoft::CommandPalette::Extensions; using IInspectable = Windows::Foundation::IInspectable; } +static winrt::com_ptr s_lastWindow; + +struct StrIconData : winrt::implements +{ + StrIconData(winrt::hstring h) : + _h{ h } {}; + winrt::hstring _h; + winrt::hstring Icon() { return _h; } + winrt::Windows::Storage::Streams::IRandomAccessStreamReference Data() { return nullptr; } +}; + +struct StrIconInfo : winrt::implements +{ + winrt::MCP::IIconData _i; + StrIconInfo(winrt::hstring h) : + _i{ winrt::make(h) } {} + winrt::MCP::IIconData Light() { return _i; } + winrt::MCP::IIconData Dark() { return _i; } +}; + +struct CommandPaletteListItem : winrt::implements +{ + struct CommandPaletteInvokableCommand : winrt::implements + { + struct HideCommandResult : winrt::implements + { + winrt::MCP::CommandResultKind Kind() { return winrt::MCP::CommandResultKind::Hide; } + winrt::MCP::ICommandResultArgs Args() { return nullptr; } + }; + + winrt::Microsoft::Terminal::Settings::Model::Command _c{ nullptr }; + CommandPaletteInvokableCommand(winrt::Microsoft::Terminal::Settings::Model::Command const& command) : + _c{ command } {} + ~CommandPaletteInvokableCommand() noexcept override = default; + // Invokable + winrt::MCP::ICommandResult Invoke(winrt::IInspectable const&) + { + auto lastPage{ s_lastWindow->GetRoot().as() }; + auto pp{ winrt::get_self(lastPage) }; + pp->Dispatcher().RunAsync(winrt::Windows::UI::Core::CoreDispatcherPriority::Normal, [=]() { + pp->DispatchAction(_c.ActionAndArgs()); + }); + return winrt::make(); + } + // Command + winrt::hstring Name() { return _c.Name(); } + winrt::hstring Id() { return _c.ID(); } + winrt::MCP::IIconInfo Icon() { return winrt::make(_c.Icon().Resolved()); } + //Notifers + wil::typed_event PropChanged; + }; + + winrt::Microsoft::Terminal::Settings::Model::Command _c{ nullptr }; + CommandPaletteListItem(winrt::Microsoft::Terminal::Settings::Model::Command const& command) : + _c{ command } {} + ~CommandPaletteListItem() noexcept override = default; + // ListItem + winrt::com_array Tags() { return {}; } + winrt::MCP::IDetails Details() { return nullptr; } + winrt::hstring Section() { return {}; } + winrt::hstring TextToSuggest() { return {}; } + // CommandItem + winrt::MCP::ICommand Command() + { + return winrt::make(_c); + } + winrt::com_array MoreCommands() { return {}; } + winrt::MCP::IIconInfo Icon() { return winrt::make(_c.Icon().Resolved()); } + winrt::hstring Title() { return _c.Name(); } + winrt::hstring Subtitle() { return _c.LanguageNeutralName(); } + //Notifers + wil::typed_event PropChanged; +}; + +struct CommandPaletteProviderTopLevelPage : winrt::implements +{ + struct ItemsChangedEventArgs : winrt::implements + { + int32_t nr; + ItemsChangedEventArgs(int32_t n) : + nr(n) {} + int32_t TotalItems() const { return nr; } + }; + CommandPaletteProviderTopLevelPage() { _items = _getItems(); } + winrt::hstring _searchText{}; + // (Dynamic)ListPage + void SearchText(winrt::hstring st) + { + _searchText = st; + p = fzf::matcher::ParsePattern(_searchText); + _items = _getItems(); + ItemsChanged.invoke(*this, winrt::make(static_cast(_items.size()))); + } + winrt::hstring SearchText() { return _searchText; } + winrt::hstring PlaceholderText() { return L"Find something new"; } + bool ShowDetails() { return false; } + winrt::MCP::IFilters Filters() { return nullptr; } + winrt::MCP::IGridProperties GridProperties() { return nullptr; } + bool HasMoreItems() { return false; } + winrt::MCP::ICommandItem EmptyContent() { return nullptr; } + void LoadMore() {} + + std::vector _items; + std::vector _getItems() + { + std::vector items; + const auto settings{ winrt::TerminalApp::implementation::AppLogic::CurrentAppSettings() }; + struct mr + { + int32_t Score; + winrt::Microsoft::Terminal::Settings::Model::Command Command; + }; + std::vector matches; + for (const auto& v : settings.ActionMap().AllCommands()) + { + auto res{ fzf::matcher::Match(v.Name(), p) }; + if (_searchText.empty() || (res && res->Score > 0)) + { + matches.emplace_back(mr{ res->Score, v }); + } + } + std::sort(std::begin(matches), std::end(matches), [](auto&& left, auto&& right) { + return left.Score > right.Score; + }); + for (const auto& [_, v] : matches) + { + items.emplace_back(winrt::make(v)); + } + return items; + } + + winrt::com_array GetItems() + { + return { std::begin(_items), std::end(_items) }; + } + + // Page + winrt::hstring Title() { return L"Windows Terminal Command Palette"; } + bool IsLoading() { return false; } + winrt::MCP::OptionalColor AccentColor() + { + return { .HasValue = false }; + } + // Command + winrt::hstring Name() { return L"Windows Terminal Commands"; } + winrt::hstring Id() { return L"doit"; } + winrt::MCP::IIconInfo Icon() { return nullptr; } + // Notifiers + wil::typed_event ItemsChanged; + wil::typed_event PropChanged; + +private: + fzf::matcher::Pattern p; +}; + +struct CommandPaletteProviderTopLevelCommandItem : winrt::implements +{ + winrt::MCP::ICommand Command() { return winrt::make(); } + winrt::com_array MoreCommands() { return {}; } + winrt::MCP::IIconInfo Icon() { return nullptr; } + winrt::hstring Title() { return L"Browse Windows Terminal Commands"; } + winrt::hstring Subtitle() { return L"Find out if it works, live"; } + wil::typed_event PropChanged; +}; + +struct CommandPaletteProvider : winrt::implements +{ + CommandPaletteProvider() + { + _theOneCommand = winrt::make(); + } + using hstring = ::winrt::hstring; + hstring Id() { return L"WindowsTerminalDev"; } + hstring DisplayName() { return L"Windows Terminal Dev"; } + winrt::MCP::IIconInfo Icon() { return nullptr; } + winrt::MCP::ICommandSettings Settings() + { + return nullptr; + } + bool Frozen() { return false; } + winrt::com_array TopLevelCommands() + { + std::vector v; + v.emplace_back(_theOneCommand); + return { v.begin(), v.end() }; + } + winrt::com_array FallbackCommands() { return {}; } + winrt::MCP::ICommand GetCommand(const hstring& id) + { + (void)id; + return nullptr; + } + wil::typed_event ItemsChanged; + void InitializeWithHost(const winrt::MCP::IExtensionHost& host) + { + _host = host; + } + void Close() + { + } + winrt::MCP::IExtensionHost _host{ nullptr }; + winrt::MCP::ICommandItem _theOneCommand; +}; + +struct CommandPaletteExtension : winrt::implements +{ + static winrt::MCP::ICommandProvider _getPaletteProvider() + { + static auto foo = winrt::make(); + return foo; + } + winrt::IInspectable GetProvider(winrt::MCP::ProviderType type) + { + switch (type) + { + case winrt::MCP::ProviderType::Commands: + return _getPaletteProvider(); + default: + FAIL_FAST(); + } + } + void Dispose() {} +}; + +struct CPXFactory : winrt::implements +{ + HRESULT __stdcall CreateInstance(IUnknown* outer, GUID const& iid, void** result) + { + *result = nullptr; + if (outer) + return CLASS_E_NOAGGREGATION; + return winrt::make_self()->QueryInterface(iid, result); + } + HRESULT __stdcall LockServer(BOOL) noexcept final { return S_OK; } +}; + //////////////////////////////////////////////////////////////////////////////// // Error message handling. This is in this file rather than with the warnings in // TerminalWindow, because the error text might be: @@ -157,6 +399,13 @@ namespace winrt::TerminalApp::implementation // including this variable in the vars we serialize in the // Remoting::CommandlineArgs up in HandleCommandlineArgs. _setupFolderPathEnvVar(); + + static auto ext = winrt::make_self(); + // {C1C5E6A5-07DB-4277-9108-D28C9129FCAF} + static const GUID cpxg = { 0xc1c5e6a5, 0x7db, 0x4277, { 0x91, 0x8, 0xd2, 0x8c, 0x91, 0x29, 0xfc, 0xaf } }; + + DWORD c; + CoRegisterClassObject(cpxg, winrt::make().get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &c); } // Method Description: @@ -507,6 +756,7 @@ namespace winrt::TerminalApp::implementation { window->SetSettingsStartupArgs(_settingsAppArgs.GetStartupActions()); } + s_lastWindow = window; return *window; } diff --git a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj index 322b30b576..8d4a25b6b5 100644 --- a/src/cascadia/TerminalApp/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/TerminalAppLib.vcxproj @@ -471,6 +471,12 @@ false false + + true + true + true + false + true false diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 8136785134..8350d3529d 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -203,6 +203,8 @@ namespace winrt::TerminalApp::implementation til::typed_event RequestLaunchPosition; + void DispatchAction(const Microsoft::Terminal::Settings::Model::ActionAndArgs& args); + WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, TitlebarBrush, PropertyChanged.raise, nullptr); WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, FrameBrush, PropertyChanged.raise, nullptr);