PRE-MERGE #18928 Add support for tmux control mode (#3656)

This commit is contained in:
Carlos Zamora 2026-01-12 16:54:45 -08:00
commit 82487d029b
45 changed files with 1969 additions and 29 deletions

View File

@ -99,7 +99,8 @@ Resources/(?!en)
^NOTICE.md
^oss/.*?/
^samples/PixelShaders/Screenshots/
^src/cascadia/TerminalSettingsEditor/SegoeFluentIconList.h$
^src/cascadia/TerminalApp/TmuxControl\.cpp$
^src/cascadia/TerminalSettingsEditor/SegoeFluentIconList\.h$
^src/interactivity/onecore/BgfxEngine\.
^src/renderer/atlas/
^src/renderer/wddmcon/WddmConRenderer\.

View File

@ -4,12 +4,13 @@
#include "pch.h"
#include "App.h"
#include "TerminalPage.h"
#include "ScratchpadContent.h"
#include "../WinRTUtils/inc/WtExeUtils.h"
#include "TerminalPage.h"
#include "TmuxControl.h"
#include "Utils.h"
#include "../../types/inc/utils.hpp"
#include "../TerminalSettingsAppAdapterLib/TerminalSettings.h"
#include "Utils.h"
#include "../WinRTUtils/inc/WtExeUtils.h"
using namespace winrt::Windows::ApplicationModel::DataTransfer;
using namespace winrt::Windows::UI::Xaml;
@ -284,6 +285,15 @@ namespace winrt::TerminalApp::implementation
const auto& activeTab{ _senderOrFocusedTab(sender) };
if constexpr (Feature_TmuxControl::IsEnabled())
{
//Tmux control takes over
if (_tmuxControl && _tmuxControl->TabIsTmuxControl(activeTab))
{
return _tmuxControl->SplitPane(activeTab, realArgs.SplitDirection());
}
}
_SplitPane(activeTab,
realArgs.SplitDirection(),
// This is safe, we're already filtering so the value is (0, 1)

View File

@ -1306,10 +1306,10 @@ void Pane::UpdateSettings(const CascadiaSettings& settings)
// - splitType: How the pane should be attached
// Return Value:
// - the new reference to the child created from the current pane.
std::shared_ptr<Pane> Pane::AttachPane(std::shared_ptr<Pane> pane, SplitDirection splitType)
std::shared_ptr<Pane> Pane::AttachPane(std::shared_ptr<Pane> pane, SplitDirection splitType, const float splitSize)
{
// Splice the new pane into the tree
const auto [first, _] = _Split(splitType, .5, pane);
const auto [first, _] = _Split(splitType, splitSize, pane);
// If the new pane has a child that was the focus, re-focus it
// to steal focus from the currently focused pane.
@ -2298,8 +2298,7 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
_firstChild->Closed(_firstClosedToken);
_secondChild->Closed(_secondClosedToken);
// If we are not a leaf we should create a new pane that contains our children
auto first = std::make_shared<Pane>(_firstChild, _secondChild, _splitState, _desiredSplitPosition);
_firstChild = first;
_firstChild = std::make_shared<Pane>(_firstChild, _secondChild, _splitState, _desiredSplitPosition);
}
else
{

View File

@ -131,7 +131,8 @@ public:
void Close();
std::shared_ptr<Pane> AttachPane(std::shared_ptr<Pane> pane,
winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType);
winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType,
const float splitSize = .5);
std::shared_ptr<Pane> DetachPane(std::shared_ptr<Pane> pane);
int GetLeafPaneCount() const noexcept;

View File

@ -923,4 +923,11 @@
<data name="InvalidRegex" xml:space="preserve">
<value>An invalid regular expression was found.</value>
</data>
</root>
<data name="TmuxControlInfo" xml:space="preserve">
<value>Running in tmux control mode; Press 'q' to detach:</value>
<comment>{Locked="'q'"}</comment>
</data>
<data name="NewTmuxControlTab.Text" xml:space="preserve">
<value>Tmux Control Tab</value>
</data>
</root>

View File

@ -173,6 +173,8 @@
<ClInclude Include="SettingsPaneContent.h">
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClInclude>
<ClInclude Include="TmuxConnection.h" />
<ClInclude Include="TmuxControl.h" />
<ClInclude Include="Toast.h" />
<ClInclude Include="TerminalSettingsCache.h" />
<ClInclude Include="SuggestionsControl.h">
@ -286,6 +288,8 @@
<DependentUpon>TerminalPaneContent.idl</DependentUpon>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="TmuxConnection.cpp" />
<ClCompile Include="TmuxControl.cpp" />
<ClCompile Include="Toast.cpp" />
<ClCompile Include="TerminalSettingsCache.cpp" />
<ClCompile Include="SuggestionsControl.cpp">
@ -295,7 +299,6 @@
<DependentUpon>MarkdownPaneContent.xaml</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
</ItemGroup>
<!-- ========================= idl Files ======================== -->
<ItemGroup>
@ -422,7 +425,6 @@
<Private>true</Private>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
<!-- This is a hack to get the ARM64 CI build working. See
@ -516,4 +518,4 @@
</ItemGroup>
</Target>
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
</Project>
</Project>

View File

@ -2,7 +2,6 @@
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
<Natvis Include="$(MSBuildThisFileDirectory)..\..\natvis\wil.natvis" />
</ItemGroup>
<ItemGroup>
<PRIResource Include="Resources\en-US\Resources.resw" />
@ -33,8 +32,9 @@
</ClCompile>
<ClCompile Include="Toast.cpp" />
<ClCompile Include="LanguageProfileNotifier.cpp" />
<ClCompile Include="Monarch.cpp" />
<ClCompile Include="Peasant.cpp" />
<ClCompile Include="TerminalSettingsCache.cpp" />
<ClCompile Include="TmuxConnection.cpp" />
<ClCompile Include="TmuxControl.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@ -60,14 +60,12 @@
<ClInclude Include="fzf/fzf.h">
<Filter>fzf</Filter>
</ClInclude>
<ClInclude Include="fzf/LICENSE">
<Filter>fzf</Filter>
</ClInclude>
<ClInclude Include="Toast.h" />
<ClInclude Include="LanguageProfileNotifier.h" />
<ClInclude Include="WindowsPackageManagerFactory.h" />
<ClInclude Include="Monarch.h" />
<ClInclude Include="Peasant.h" />
<ClInclude Include="TerminalSettingsCache.h" />
<ClInclude Include="TmuxConnection.h" />
<ClInclude Include="TmuxControl.h" />
</ItemGroup>
<ItemGroup>
<Midl Include="AppLogic.idl">
@ -90,7 +88,8 @@
<Midl Include="TerminalWindow.idl" />
<Midl Include="TaskbarState.idl" />
<Midl Include="IPaneContent.idl" />
<Midl Include="Monarch.idl" />
<Midl Include="Remoting.idl" />
<Midl Include="HighlightedTextControl.idl" />
</ItemGroup>
<ItemGroup>
<Page Include="MinMaxCloseControl.xaml">
@ -123,6 +122,7 @@
<Page Include="AboutDialog.xaml" />
<Page Include="SuggestionsControl.xaml" />
<Page Include="SnippetsPaneContent.xaml" />
<Page Include="MarkdownPaneContent.xaml" />
</ItemGroup>
<ItemGroup>
<Filter Include="app">
@ -155,4 +155,4 @@
<Filter>app</Filter>
</ApplicationDefinition>
</ItemGroup>
</Project>
</Project>

View File

@ -23,6 +23,7 @@
#include "SnippetsPaneContent.h"
#include "TabRowControl.h"
#include "TerminalSettingsCache.h"
#include "TmuxControl.h"
#include "LaunchPositionRequest.g.cpp"
#include "RenameWindowRequestedArgs.g.cpp"
@ -406,6 +407,15 @@ namespace winrt::TerminalApp::implementation
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
if constexpr (Feature_TmuxControl::IsEnabled())
{
// tmux control takes over
if (page->_tmuxControl && page->_tmuxControl->TabIsTmuxControl(page->_GetFocusedTabImpl()))
{
return;
}
}
page->_OpenNewTerminalViaDropdown(NewTerminalArgs());
}
});
@ -1431,6 +1441,15 @@ namespace winrt::TerminalApp::implementation
}
if (altPressed && !debugTap)
{
// tmux control panes don't share tab with other panes
if constexpr (Feature_TmuxControl::IsEnabled())
{
if (_tmuxControl && _tmuxControl->TabIsTmuxControl(_GetFocusedTabImpl()))
{
return;
}
}
this->_SplitPane(_GetFocusedTabImpl(),
SplitDirection::Automatic,
0.5f,
@ -2527,6 +2546,15 @@ namespace winrt::TerminalApp::implementation
return false;
}
if constexpr (Feature_TmuxControl::IsEnabled())
{
//Tmux control tab doesn't support to drag
if (_tmuxControl && _tmuxControl->TabIsTmuxControl(tab))
{
return false;
}
}
// If there was a windowId in the action, try to move it to the
// specified window instead of moving it in our tab row.
const auto windowId{ args.Window() };
@ -3595,6 +3623,26 @@ namespace winrt::TerminalApp::implementation
original->SetActive();
}
if constexpr (Feature_TmuxControl::IsEnabled())
{
if (!_tmuxControl)
{
_tmuxControl = std::make_shared<TmuxControl>(*this);
}
control.EnterTmuxControl([tmuxControl = _tmuxControl.get()](auto&& sender, auto&& args) {
if (auto control = sender.try_as<TermControl>())
{
if (tmuxControl->AcquireSingleUseLock(std::move(control)))
{
args.InputCallback([tmuxControl](auto&& str) {
tmuxControl->FeedInput(winrt_array_to_wstring_view(str));
});
}
}
});
}
return resultPane;
}
@ -5500,6 +5548,15 @@ namespace winrt::TerminalApp::implementation
tabImpl.copy_from(winrt::get_self<Tab>(tabBase));
if (tabImpl)
{
if constexpr (Feature_TmuxControl::IsEnabled())
{
//Tmux control tab doesn't support to drag
if (_tmuxControl && _tmuxControl->TabIsTmuxControl(tabImpl))
{
return;
}
}
// First: stash the tab we started dragging.
// We're going to be asked for this.
_stashed.draggedTab = tabImpl;

View File

@ -37,6 +37,7 @@ namespace winrt::Microsoft::Terminal::Settings
namespace winrt::TerminalApp::implementation
{
struct TerminalSettingsCache;
struct TmuxControl;
inline constexpr uint32_t DefaultRowsToScroll{ 3 };
inline constexpr std::wstring_view TabletInputServiceKey{ L"TabletInputService" };
@ -256,6 +257,7 @@ namespace winrt::TerminalApp::implementation
std::vector<std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs>> _previouslyClosedPanesAndTabs{};
uint32_t _systemRowsToScroll{ DefaultRowsToScroll };
std::shared_ptr<TmuxControl> _tmuxControl{ nullptr };
// use a weak reference to prevent circular dependency with AppLogic
winrt::weak_ref<winrt::TerminalApp::IDialogPresenter> _dialogPresenter;
@ -580,6 +582,7 @@ namespace winrt::TerminalApp::implementation
friend class TerminalAppLocalTests::TabTests;
friend class TerminalAppLocalTests::SettingsTests;
friend struct TmuxControl;
};
}

View File

@ -0,0 +1,48 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "TmuxConnection.h"
namespace winrt::TerminalApp::implementation
{
void TmuxConnection::Initialize(const Windows::Foundation::Collections::ValueSet&) const noexcept
{
}
void TmuxConnection::Start() noexcept
{
}
void TmuxConnection::WriteInput(const winrt::array_view<const char16_t> buffer)
{
TerminalInput.raise(buffer);
}
void TmuxConnection::Resize(uint32_t /*rows*/, uint32_t /*columns*/) noexcept
{
}
void TmuxConnection::Close() noexcept
{
StateChanged.raise(*this, nullptr);
}
winrt::guid TmuxConnection::SessionId() const noexcept
{
return {};
}
Microsoft::Terminal::TerminalConnection::ConnectionState TmuxConnection::State() const noexcept
{
return Microsoft::Terminal::TerminalConnection::ConnectionState::Connected;
}
void TmuxConnection::WriteOutput(const winrt::array_view<const char16_t> wstr)
{
if (!wstr.empty())
{
TerminalOutput.raise(wstr);
}
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
namespace winrt::TerminalApp::implementation
{
struct TmuxConnection : winrt::implements<TmuxConnection, Microsoft::Terminal::TerminalConnection::ITerminalConnection>
{
// ITerminalConnection methods
void Initialize(const Windows::Foundation::Collections::ValueSet& /*settings*/) const noexcept;
void Start() noexcept;
void WriteInput(winrt::array_view<const char16_t> buffer);
void Resize(uint32_t rows, uint32_t columns) noexcept;
void Close() noexcept;
til::event<Microsoft::Terminal::TerminalConnection::TerminalOutputHandler> TerminalOutput;
til::typed_event<Microsoft::Terminal::TerminalConnection::ITerminalConnection, IInspectable> StateChanged;
winrt::guid SessionId() const noexcept;
Microsoft::Terminal::TerminalConnection::ConnectionState State() const noexcept;
// TmuxConnection methods
void WriteOutput(winrt::array_view<const char16_t> wstr);
til::event<Microsoft::Terminal::TerminalConnection::TerminalOutputHandler> TerminalInput;
};
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,180 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
class Pane;
namespace winrt::TerminalApp::implementation
{
struct Tab;
struct TerminalPage;
struct TmuxConnection;
struct TmuxControl : std::enable_shared_from_this<TmuxControl>
{
TmuxControl(TerminalPage& page);
bool AcquireSingleUseLock(winrt::Microsoft::Terminal::Control::TermControl control) noexcept;
bool TabIsTmuxControl(const winrt::com_ptr<Tab>& tab);
void SplitPane(const winrt::com_ptr<Tab>& tab, winrt::Microsoft::Terminal::Settings::Model::SplitDirection direction);
void FeedInput(std::wstring_view str);
private:
enum class State
{
Init,
Attaching,
Attached,
};
enum class ResponseInfoType
{
Ignore,
DiscoverNewWindow,
DiscoverWindows,
CapturePane,
DiscoverPanes,
};
struct ResponseInfo
{
ResponseInfoType type;
union
{
struct
{
int64_t paneId;
} capturePane;
} data;
};
enum class TmuxLayoutType
{
// A single leaf pane
Pane,
// Indicates the start of a horizontal split layout
PushHorizontal,
// Indicates the start of a vertical split layout
PushVertical,
// Indicates the end of the most recent split layout
Pop,
};
struct TmuxLayout
{
TmuxLayoutType type = TmuxLayoutType::Pane;
// Only set for: Pane, PushHorizontal, PushVertical
til::CoordType width = 0;
// Only set for: Pane, PushHorizontal, PushVertical
til::CoordType height = 0;
// Only set for: Pane
int64_t id = -1;
};
struct AttachedPane
{
AttachedPane() = default;
AttachedPane(int64_t paneId, std::wstring outputBacklog);
~AttachedPane();
// Have to redefine them because they get implicitly deleted once a destructor is defined.
AttachedPane(AttachedPane&&) = default;
AttachedPane& operator=(AttachedPane&&) = default;
// Why would you want to copy this.
AttachedPane(const AttachedPane&) = delete;
AttachedPane& operator=(const AttachedPane&) = delete;
int64_t windowId = -1;
int64_t paneId = -1;
winrt::com_ptr<TmuxConnection> connection{ nullptr };
winrt::Microsoft::Terminal::Control::TermControl control{ nullptr };
std::wstring outputBacklog;
bool initialized = false;
bool ignoreOutput = false;
};
safe_void_coroutine _parseLine(std::wstring line);
void _handleAttach(); // A special case of _handleResponse()
void _handleDetach();
void _handleSessionChanged(int64_t sessionId);
void _handleWindowAdd(int64_t windowId);
void _handleWindowRenamed(int64_t windowId, winrt::hstring name);
void _handleWindowClose(int64_t windowId);
void _handleWindowPaneChanged(int64_t windowId, int64_t paneId);
void _handleLayoutChange(int64_t windowId, std::wstring_view layout);
void _handleResponse(std::wstring_view result);
void _sendSetOption(std::wstring_view option);
void _sendDiscoverWindows(int64_t sessionId);
void _handleResponseDiscoverWindows(std::wstring_view response);
void _sendDiscoverNewWindow(int64_t windowId);
void _handleResponseDiscoverNewWindow(std::wstring_view response);
void _sendCapturePane(int64_t paneId, til::CoordType history);
void _handleResponseCapturePane(const ResponseInfo& info, std::wstring_view response);
void _sendDiscoverPanes(int64_t windowId);
void _handleResponseDiscoverPanes(std::wstring_view response);
void _sendNewWindow();
void _sendKillWindow(int64_t windowId);
void _sendKillPane(int64_t paneId);
void _sendSplitPane(std::shared_ptr<Pane> pane, winrt::Microsoft::Terminal::Settings::Model::SplitDirection direction);
void _sendSelectWindow(int64_t windowId);
void _sendSelectPane(int64_t paneId);
void _sendResizeWindow(int64_t windowId, til::CoordType width, til::CoordType height);
void _sendResizePane(int64_t paneId, til::CoordType width, til::CoordType height);
void _sendSendKey(int64_t paneId, const std::wstring_view keys);
void _sendIgnoreResponse(wil::zwstring_view cmd);
void _sendWithResponseInfo(wil::zwstring_view cmd, ResponseInfo info);
std::shared_ptr<Pane> _layoutCreateRecursive(int64_t windowId, std::wstring_view& remaining, TmuxLayout parent);
std::wstring_view _layoutStripHash(std::wstring_view str);
TmuxLayout _layoutParseNextToken(std::wstring_view& remaining);
void _deliverOutputToPane(int64_t paneId, const std::wstring_view text);
winrt::com_ptr<Tab> _getTab(int64_t windowId) const;
void _newTab(int64_t windowId, winrt::hstring name, std::shared_ptr<Pane> pane);
std::pair<AttachedPane&, std::shared_ptr<Pane>> _newPane(int64_t windowId, int64_t paneId);
void _openNewTerminalViaDropdown();
TerminalPage& _page; // Non-owning, because TerminalPage owns us
winrt::Windows::System::DispatcherQueue _dispatcherQueue{ nullptr };
winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _newTabMenu;
winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr };
winrt::com_ptr<Tab> _controlTab{ nullptr };
winrt::Microsoft::Terminal::Settings::Model::Profile _profile{ nullptr };
State _state = State::Init;
bool _inUse = false;
std::wstring _lineBuffer;
std::wstring _responseBuffer;
bool _insideOutputBlock = false;
winrt::event_token _detachKeyDownRevoker;
winrt::event_token _windowSizeChangedRevoker;
winrt::event_token _newTabClickRevoker;
std::deque<ResponseInfo> _commandQueue;
std::unordered_map<int64_t, AttachedPane> _attachedPanes;
std::unordered_map<int64_t, winrt::com_ptr<Tab>> _attachedWindows;
int64_t _sessionId = -1;
int64_t _activePaneId = -1;
int64_t _activeWindowId = -1;
til::CoordType _terminalWidth = 0;
til::CoordType _terminalHeight = 0;
winrt::Windows::UI::Xaml::Thickness _thickness{ 0, 0, 0, 0 };
float _fontWidth = 0;
float _fontHeight = 0;
std::pair<std::shared_ptr<Pane>, winrt::Microsoft::Terminal::Settings::Model::SplitDirection> _splittingPane{
nullptr,
winrt::Microsoft::Terminal::Settings::Model::SplitDirection::Right,
};
};
}

View File

@ -15,6 +15,7 @@
#include "../../renderer/atlas/AtlasEngine.h"
#include "../../renderer/base/renderer.hpp"
#include "../../renderer/uia/UiaRenderer.hpp"
#include "../../terminal/adapter/adaptDispatch.hpp"
#include "../../types/inc/CodepointWidthDetector.hpp"
#include "../../types/inc/utils.hpp"
@ -139,6 +140,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto pfnWindowSizeChanged = [this](auto&& PH1, auto&& PH2) { _terminalWindowSizeChanged(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2)); };
_terminal->SetWindowSizeChangedCallback(pfnWindowSizeChanged);
_terminal->SetEnterTmuxControlCallback([this]() -> std::function<bool(wchar_t)> {
const auto args = winrt::make_self<EnterTmuxControlEventArgs>();
EnterTmuxControl.raise(*this, *args);
if (auto inputCallback = args->InputCallback())
{
return [inputCallback = std::move(inputCallback)](wchar_t ch) -> bool {
const auto c16 = static_cast<char16_t>(ch);
inputCallback({ &c16, 1 });
return true;
};
}
return nullptr;
});
// MSFT 33353327: Initialize the renderer in the ctor instead of Initialize().
// We need the renderer to be ready to accept new engines before the SwapChainPanel is ready to go.
// If we wait, a screen reader may try to get the AutomationPeer (aka the UIA Engine), and we won't be able to attach
@ -1459,6 +1474,45 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->TrySnapOnInput();
}
void ControlCore::InjectTextAtCursor(const winrt::hstring& text)
{
if (text.empty())
{
return;
}
const auto lock = _terminal->LockForWriting();
std::wstring_view remaining{ text };
// Process one line at a time
for (;;)
{
// Get the (CR)LF position
const auto lf = std::min(remaining.size(), remaining.find(L'\n'));
// Strip off the CR
auto lineEnd = lf;
if (lineEnd != 0 && remaining[lineEnd - 1] == L'\r')
{
lineEnd -= 1;
}
// Split into line and whatever comes after
const auto line = remaining.substr(0, lineEnd);
remaining = remaining.substr(std::min(remaining.size(), lf + 1));
// This will not just print the line but also handle delay wrap, etc.
_terminal->GetAdaptDispatch().PrintString(line);
if (remaining.empty())
{
break;
}
_terminal->GetAdaptDispatch().LineFeed(DispatchTypes::LineFeedType::DependsOnMode);
}
}
FontInfo ControlCore::GetFont() const
{
return _actualFont;
@ -1568,6 +1622,17 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _terminal->GetViewport().Height();
}
// Function Description:
// - Gets the width of the terminal in lines of text. This is just the
// width of the viewport.
// Return Value:
// - The width of the terminal in lines of text
int ControlCore::ViewWidth() const
{
const auto lock = _terminal->LockForReading();
return _terminal->GetViewport().Width();
}
// Function Description:
// - Gets the height of the terminal in lines of text. This includes the
// history AND the viewport.

View File

@ -23,6 +23,7 @@
#include "../../buffer/out/search.h"
#include "../../cascadia/TerminalCore/Terminal.hpp"
#include "../../renderer/inc/FontInfoDesired.hpp"
#include "../../terminal/adapter/ITermDispatch.hpp"
namespace Microsoft::Console::Render::Atlas
{
@ -124,6 +125,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void SendInput(std::wstring_view wstr);
void PasteText(const winrt::hstring& hstr);
void InjectTextAtCursor(const winrt::hstring& text);
bool CopySelectionToClipboard(bool singleLine, bool withControlSequences, const CopyFormat formats);
void SelectAll();
void ClearSelection();
@ -172,6 +174,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
int ScrollOffset();
int ViewHeight() const;
int ViewWidth() const;
int BufferHeight() const;
bool HasSelection() const;
@ -295,10 +298,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
til::typed_event<IInspectable, Control::SearchMissingCommandEventArgs> SearchMissingCommand;
til::typed_event<> RefreshQuickFixUI;
til::typed_event<IInspectable, Control::WindowSizeChangedEventArgs> WindowSizeChanged;
til::typed_event<IInspectable, Control::EnterTmuxControlEventArgs> EnterTmuxControl;
til::typed_event<> CloseTerminalRequested;
til::typed_event<> RestartTerminalRequested;
til::typed_event<> Attached;
// clang-format on

View File

@ -128,6 +128,7 @@ namespace Microsoft.Terminal.Control
Microsoft.Terminal.Core.ControlKeyStates modifiers);
void SendInput(String text);
void PasteText(String text);
void InjectTextAtCursor(String text);
void SelectAll();
void ClearSelection();
Boolean ToggleBlockSelection();
@ -198,6 +199,7 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, SearchMissingCommandEventArgs> SearchMissingCommand;
event Windows.Foundation.TypedEventHandler<Object, Object> RefreshQuickFixUI;
event Windows.Foundation.TypedEventHandler<Object, WindowSizeChangedEventArgs> WindowSizeChanged;
event Windows.Foundation.TypedEventHandler<Object, EnterTmuxControlEventArgs> EnterTmuxControl;
// These events are always called from the UI thread (bugs aside)
event Windows.Foundation.TypedEventHandler<Object, FontSizeChangedArgs> FontSizeChanged;

View File

@ -21,3 +21,4 @@
#include "StringSentEventArgs.g.cpp"
#include "SearchMissingCommandEventArgs.g.cpp"
#include "WindowSizeChangedEventArgs.g.cpp"
#include "EnterTmuxControlEventArgs.g.cpp"

View File

@ -21,6 +21,7 @@
#include "StringSentEventArgs.g.h"
#include "SearchMissingCommandEventArgs.g.h"
#include "WindowSizeChangedEventArgs.g.h"
#include "EnterTmuxControlEventArgs.g.h"
namespace winrt::Microsoft::Terminal::Control::implementation
{
@ -265,6 +266,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WINRT_PROPERTY(int32_t, Width);
WINRT_PROPERTY(int32_t, Height);
};
struct EnterTmuxControlEventArgs : public EnterTmuxControlEventArgsT<EnterTmuxControlEventArgs>
{
til::property<TmuxControlInputCallback> InputCallback;
};
}
namespace winrt::Microsoft::Terminal::Control::factory_implementation

View File

@ -159,4 +159,11 @@ namespace Microsoft.Terminal.Control
Int32 Width;
Int32 Height;
}
delegate void TmuxControlInputCallback(Char[] input);
runtimeclass EnterTmuxControlEventArgs
{
void InputCallback(TmuxControlInputCallback callback);
}
}

View File

@ -40,6 +40,7 @@ namespace Microsoft.Terminal.Control
Int32 ScrollOffset { get; };
Int32 ViewHeight { get; };
Int32 ViewWidth { get; };
Int32 BufferHeight { get; };
Boolean HasSelection { get; };

View File

@ -330,6 +330,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_revokers.SearchMissingCommand = _core.SearchMissingCommand(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleSearchMissingCommand });
_revokers.WindowSizeChanged = _core.WindowSizeChanged(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleWindowSizeChanged });
_revokers.WriteToClipboard = _core.WriteToClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleWriteToClipboard });
_revokers.EnterTmuxControl = _core.EnterTmuxControl(winrt::auto_revoke, { get_weak(), &TermControl::_bubbleEnterTmuxControl });
_revokers.PasteFromClipboard = _interactivity.PasteFromClipboard(winrt::auto_revoke, { get_weak(), &TermControl::_bubblePasteFromClipboard });
@ -1506,6 +1507,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_core.SendInput(text);
}
void TermControl::InjectTextAtCursor(const winrt::hstring& text)
{
_core.InjectTextAtCursor(text);
}
// Method Description:
// - Manually handles key events for certain keys that can't be passed to us
// normally. Namely, the keys we're concerned with are F7 down and Alt up.
@ -2667,6 +2673,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return _core.ViewHeight();
}
int TermControl::ViewWidth() const
{
return _core.ViewWidth();
}
int TermControl::BufferHeight() const
{
return _core.BufferHeight();
@ -2866,7 +2877,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
else
{
// Do we ever get here (= uninitialized terminal)? If so: How?
assert(false);
// Yes, we can get here, when do Pane._Split, it need to call _SetupEntranceAnimation
// which need the control's size, while this size can only be available when the control
// is initialized.
return { 10, 10 };
}
}
@ -4002,6 +4015,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}
void TermControl::_bubbleEnterTmuxControl(const IInspectable&, Control::EnterTmuxControlEventArgs args)
{
EnterTmuxControl.raise(*this, std::move(args));
}
til::CoordType TermControl::_calculateSearchScrollOffset() const
{
auto result = 0;

View File

@ -97,6 +97,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
int ScrollOffset() const;
int ViewHeight() const;
int ViewWidth() const;
int BufferHeight() const;
bool HasSelection() const;
@ -182,6 +183,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool RawWriteKeyEvent(const WORD vkey, const WORD scanCode, const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers, const bool keyDown);
bool RawWriteChar(const wchar_t character, const WORD scanCode, const winrt::Microsoft::Terminal::Core::ControlKeyStates modifiers);
void RawWriteString(const winrt::hstring& text);
void InjectTextAtCursor(const winrt::hstring& text);
void ShowContextMenu();
bool OpenQuickFixMenu();
@ -217,6 +219,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
til::typed_event<IInspectable, Control::StringSentEventArgs> StringSent;
til::typed_event<IInspectable, Control::SearchMissingCommandEventArgs> SearchMissingCommand;
til::typed_event<IInspectable, Control::WindowSizeChangedEventArgs> WindowSizeChanged;
til::typed_event<IInspectable, Control::EnterTmuxControlEventArgs> EnterTmuxControl;
// UNDER NO CIRCUMSTANCES SHOULD YOU ADD A (PROJECTED_)FORWARDED_TYPED_EVENT HERE
// Those attach the handler to the core directly, and will explode if
@ -437,6 +440,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _bubbleSearchMissingCommand(const IInspectable& sender, const Control::SearchMissingCommandEventArgs& args);
winrt::fire_and_forget _bubbleWindowSizeChanged(const IInspectable& sender, Control::WindowSizeChangedEventArgs args);
void _bubbleEnterTmuxControl(const IInspectable& sender, Control::EnterTmuxControlEventArgs args);
til::CoordType _calculateSearchScrollOffset() const;
void _PasteCommandHandler(const IInspectable& sender, const IInspectable& args);
@ -471,6 +475,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Control::ControlCore::SearchMissingCommand_revoker SearchMissingCommand;
Control::ControlCore::RefreshQuickFixUI_revoker RefreshQuickFixUI;
Control::ControlCore::WindowSizeChanged_revoker WindowSizeChanged;
Control::ControlCore::EnterTmuxControl_revoker EnterTmuxControl;
// These are set up in _InitializeTerminal
Control::ControlCore::RendererWarning_revoker RendererWarning;

View File

@ -74,6 +74,7 @@ namespace Microsoft.Terminal.Control
event Windows.Foundation.TypedEventHandler<Object, Object> ReadOnlyChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> FocusFollowMouseRequested;
event Windows.Foundation.TypedEventHandler<Object, WindowSizeChangedEventArgs> WindowSizeChanged;
event Windows.Foundation.TypedEventHandler<Object, EnterTmuxControlEventArgs> EnterTmuxControl;
event Windows.Foundation.TypedEventHandler<Object, CompletionsChangedEventArgs> CompletionsChanged;
@ -130,6 +131,7 @@ namespace Microsoft.Terminal.Control
Boolean RawWriteKeyEvent(UInt16 vkey, UInt16 scanCode, Microsoft.Terminal.Core.ControlKeyStates modifiers, Boolean keyDown);
Boolean RawWriteChar(Char character, UInt16 scanCode, Microsoft.Terminal.Core.ControlKeyStates modifiers);
void RawWriteString(String text);
void InjectTextAtCursor(String text);
void BellLightOn();

View File

@ -51,6 +51,7 @@ void Terminal::Create(til::size viewportSize, til::CoordType scrollbackLines, Re
_mainBuffer = std::make_unique<TextBuffer>(bufferSize, attr, cursorSize, true, &renderer);
auto dispatch = std::make_unique<AdaptDispatch>(*this, &renderer, _renderSettings, _terminalInput);
_adaptDispatch = dispatch.get();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
_stateMachine = std::make_unique<StateMachine>(std::move(engine));
}
@ -1039,6 +1040,12 @@ bool Terminal::IsFocused() const noexcept
return _focused;
}
AdaptDispatch& Microsoft::Terminal::Core::Terminal::GetAdaptDispatch() noexcept
{
_assertLocked();
return *_adaptDispatch;
}
RenderSettings& Terminal::GetRenderSettings() noexcept
{
_assertLocked();
@ -1270,6 +1277,11 @@ void Microsoft::Terminal::Core::Terminal::SetSearchMissingCommandCallback(std::f
_pfnSearchMissingCommand.swap(pfn);
}
void Terminal::SetEnterTmuxControlCallback(std::function<std::function<bool(wchar_t)>()> pfn) noexcept
{
_pfnEnterTmuxControl = std::move(pfn);
}
void Microsoft::Terminal::Core::Terminal::SetClearQuickFixCallback(std::function<void()> pfn) noexcept
{
_pfnClearQuickFix.swap(pfn);

View File

@ -116,6 +116,7 @@ public:
int ViewEndIndex() const noexcept;
bool IsFocused() const noexcept;
::Microsoft::Console::VirtualTerminal::AdaptDispatch& GetAdaptDispatch() noexcept;
RenderSettings& GetRenderSettings() noexcept;
const RenderSettings& GetRenderSettings() const noexcept;
@ -153,14 +154,12 @@ public:
void ShowWindow(bool showOrHide) override;
void UseAlternateScreenBuffer(const TextAttribute& attrs) override;
void UseMainScreenBuffer() override;
bool IsVtInputEnabled() const noexcept override;
void NotifyBufferRotation(const int delta) override;
void NotifyShellIntegrationMark() override;
void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) override;
void SearchMissingCommand(const std::wstring_view command) override;
std::function<bool(wchar_t)> EnterTmuxControl() override;
#pragma endregion
@ -230,6 +229,7 @@ public:
void SetPlayMidiNoteCallback(std::function<void(const int, const int, const std::chrono::microseconds)> pfn) noexcept;
void CompletionsChangedCallback(std::function<void(std::wstring_view, unsigned int)> pfn) noexcept;
void SetSearchMissingCommandCallback(std::function<void(std::wstring_view, const til::CoordType)> pfn) noexcept;
void SetEnterTmuxControlCallback(std::function<std::function<bool(wchar_t)>()> pfn) noexcept;
void SetClearQuickFixCallback(std::function<void()> pfn) noexcept;
void SetWindowSizeChangedCallback(std::function<void(int32_t, int32_t)> pfn) noexcept;
void SetSearchHighlights(const std::vector<til::point_span>& highlights) noexcept;
@ -338,10 +338,12 @@ private:
std::function<void(const int, const int, const std::chrono::microseconds)> _pfnPlayMidiNote;
std::function<void(std::wstring_view, unsigned int)> _pfnCompletionsChanged;
std::function<void(std::wstring_view, const til::CoordType)> _pfnSearchMissingCommand;
std::function<std::function<bool(wchar_t)>()> _pfnEnterTmuxControl;
std::function<void()> _pfnClearQuickFix;
std::function<void(int32_t, int32_t)> _pfnWindowSizeChanged;
RenderSettings _renderSettings;
::Microsoft::Console::VirtualTerminal::AdaptDispatch* _adaptDispatch = nullptr;
std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;
::Microsoft::Console::VirtualTerminal::TerminalInput _terminalInput;

View File

@ -364,6 +364,11 @@ void Terminal::SearchMissingCommand(const std::wstring_view command)
}
}
std::function<bool(wchar_t)> Terminal::EnterTmuxControl()
{
return _pfnEnterTmuxControl ? _pfnEnterTmuxControl() : nullptr;
}
void Terminal::NotifyBufferRotation(const int delta)
{
// Update our selection, so it doesn't move as the buffer is cycled

View File

@ -352,6 +352,7 @@ namespace winrt::Microsoft::Terminal::Settings
_AllowVtChecksumReport = profile.AllowVtChecksumReport();
_AllowVtClipboardWrite = profile.AllowVtClipboardWrite();
_PathTranslationStyle = profile.PathTranslationStyle();
_AllowTmuxControl = profile.AllowTmuxControl();
}
// Method Description:

View File

@ -92,6 +92,7 @@ namespace winrt::Microsoft::Terminal::Settings
SIMPLE_OVERRIDABLE_SETTING(bool, Elevate, false);
SIMPLE_OVERRIDABLE_SETTING(IEnvironmentVariableMapView, EnvironmentVariables, nullptr);
SIMPLE_OVERRIDABLE_SETTING(bool, ReloadEnvironmentVariables, true);
SIMPLE_OVERRIDABLE_SETTING(bool, AllowTmuxControl, false);
public:
// TerminalApp overrides these when duplicating a session

View File

@ -94,6 +94,11 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
_NotifyChanges(L"Icon", L"IconPath");
}
constexpr bool TmuxControlEnabled() noexcept
{
return Feature_TmuxControl::IsEnabled();
}
// starting directory
hstring CurrentStartingDirectoryPreview() const;
bool UseParentProcessDirectory() const;
@ -161,6 +166,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
OBSERVABLE_PROJECTED_SETTING(_profile, AnswerbackMessage);
OBSERVABLE_PROJECTED_SETTING(_profile, RainbowSuggestions);
OBSERVABLE_PROJECTED_SETTING(_profile, PathTranslationStyle);
OBSERVABLE_PROJECTED_SETTING(_profile, AllowTmuxControl);
WINRT_PROPERTY(bool, IsBaseLayer, false);
WINRT_PROPERTY(bool, FocusDeleteButton, false);

View File

@ -108,6 +108,7 @@ namespace Microsoft.Terminal.Settings.Editor
Boolean UsingBuiltInIcon { get; };
Boolean UsingEmojiIcon { get; };
Boolean UsingImageIcon { get; };
Boolean TmuxControlEnabled { get; };
String IconPath;
EnumEntry CurrentBuiltInIcon;
@ -156,5 +157,6 @@ namespace Microsoft.Terminal.Settings.Editor
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, RainbowSuggestions);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Microsoft.Terminal.Control.PathTranslationStyle, PathTranslationStyle);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AllowVtClipboardWrite);
OBSERVABLE_PROJECTED_PROFILE_SETTING(Boolean, AllowTmuxControl);
}
}

View File

@ -82,6 +82,16 @@
<TextBox Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind Profile.AnswerbackMessage, Mode=TwoWay}" />
</local:SettingContainer>
<!-- Allow Tmux Control -->
<local:SettingContainer x:Uid="Profile_AllowTmuxControl"
ClearSettingValue="{x:Bind Profile.ClearAllowTmuxControl}"
HasSettingValue="{x:Bind Profile.HasAllowTmuxControl, Mode=OneWay}"
SettingOverrideSource="{x:Bind Profile.AllowTmuxControlOverrideSource, Mode=OneWay}"
Visibility="{x:Bind Profile.TmuxControlEnabled, Mode=OneTime}">
<ToggleSwitch IsOn="{x:Bind Profile.AllowTmuxControl, Mode=TwoWay}"
Style="{StaticResource ToggleSwitchInExpanderStyle}" />
</local:SettingContainer>
</StackPanel>
</Grid>
</Page>

View File

@ -542,6 +542,10 @@
<value>Always on top</value>
<comment>Header for a control to toggle if the app will always be presented on top of other windows, or is treated normally (when disabled).</comment>
</data>
<data name="Profile_AllowTmuxControl.Header" xml:space="preserve">
<value>Allow Tmux Control</value>
<comment>Header for a control to toggle tmux control.</comment>
</data>
<data name="Profile_ForceVTInput.Header" xml:space="preserve">
<value>Use the legacy input encoding</value>
<comment>Header for a control to toggle legacy input encoding for the terminal.</comment>

View File

@ -106,6 +106,7 @@ Author(s):
X(bool, AllowVtChecksumReport, "compatibility.allowDECRQCRA", false) \
X(bool, AllowVtClipboardWrite, "compatibility.allowOSC52", true) \
X(bool, AllowKeypadMode, "compatibility.allowDECNKM", false) \
X(bool, AllowTmuxControl, "AllowTmuxControl", false) \
X(Microsoft::Terminal::Control::PathTranslationStyle, PathTranslationStyle, "pathTranslationStyle", Microsoft::Terminal::Control::PathTranslationStyle::None)
// Intentionally omitted Profile settings:

View File

@ -91,6 +91,7 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_PROFILE_SETTING(Boolean, AllowVtChecksumReport);
INHERITABLE_PROFILE_SETTING(Boolean, AllowKeypadMode);
INHERITABLE_PROFILE_SETTING(Boolean, AllowVtClipboardWrite);
INHERITABLE_PROFILE_SETTING(Boolean, AllowTmuxControl);
INHERITABLE_PROFILE_SETTING(Microsoft.Terminal.Control.PathTranslationStyle, PathTranslationStyle);
}

View File

@ -187,4 +187,16 @@
<alwaysDisabledReleaseTokens/>
</feature>
<feature>
<name>Feature_TmuxControl</name>
<description>Enables Tmux Control</description>
<id>3656</id>
<stage>AlwaysDisabled</stage>
<alwaysEnabledBrandingTokens>
<brandingToken>Dev</brandingToken>
<brandingToken>Canary</brandingToken>
<brandingToken>Preview</brandingToken>
</alwaysEnabledBrandingTokens>
</feature>
</featureStaging>

View File

@ -427,7 +427,14 @@ void ConhostInternalGetSet::InvokeCompletions(std::wstring_view /*menuJson*/, un
{
// Not implemented for conhost.
}
void ConhostInternalGetSet::SearchMissingCommand(std::wstring_view /*missingCommand*/)
{
// Not implemented for conhost.
}
std::function<bool(wchar_t)> ConhostInternalGetSet::EnterTmuxControl()
{
// Not implemented for conhost.
return {};
}

View File

@ -71,6 +71,7 @@ public:
void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) override;
void SearchMissingCommand(std::wstring_view missingCommand) override;
std::function<bool(wchar_t)> EnterTmuxControl() override;
private:
Microsoft::Console::IIoProvider& _io;

View File

@ -192,6 +192,8 @@ public:
virtual void PlaySounds(const VTParameters parameters) = 0; // DECPS
virtual void SetOptionalFeatures(const til::enumset<OptionalFeature> features) = 0;
virtual StringHandler EnterTmuxControl(const VTParameters parameters) = 0; // tmux -CC
};
inline Microsoft::Console::VirtualTerminal::ITermDispatch::~ITermDispatch() = default;
#pragma warning(pop)

View File

@ -90,5 +90,6 @@ namespace Microsoft::Console::VirtualTerminal
virtual void InvokeCompletions(std::wstring_view menuJson, unsigned int replaceLength) = 0;
virtual void SearchMissingCommand(const std::wstring_view command) = 0;
virtual std::function<bool(wchar_t)> EnterTmuxControl() = 0;
};
}

View File

@ -4760,3 +4760,12 @@ void AdaptDispatch::SetOptionalFeatures(const til::enumset<OptionalFeature> feat
{
_optionalFeatures = features;
}
ITermDispatch::StringHandler AdaptDispatch::EnterTmuxControl(const VTParameters parameters)
{
if (parameters.size() != 1 || parameters.at(0).value() != 1000)
{
return nullptr;
}
return _api.EnterTmuxControl();
}

View File

@ -190,6 +190,8 @@ namespace Microsoft::Console::VirtualTerminal
void SetOptionalFeatures(const til::enumset<OptionalFeature> features) noexcept override;
StringHandler EnterTmuxControl(const VTParameters parameters) override; // tmux -CC
private:
enum class Mode
{

View File

@ -179,6 +179,8 @@ public:
void PlaySounds(const VTParameters /*parameters*/) override{}; // DECPS
void SetOptionalFeatures(const til::enumset<OptionalFeature> /*features*/) override{};
StringHandler EnterTmuxControl(const VTParameters /*parameters*/) override { return nullptr; }; // tmux -CC
};
#pragma warning(default : 26440) // Restore "can be declared noexcept" warning

View File

@ -224,6 +224,12 @@ public:
Log::Comment(L"SearchMissingCommand MOCK called...");
}
std::function<bool(wchar_t)> EnterTmuxControl() override
{
Log::Comment(L"EnterTmuxControl MOCK called...");
return nullptr;
}
void PrepData()
{
PrepData(CursorDirection::UP); // if called like this, the cursor direction doesn't matter.

View File

@ -724,6 +724,9 @@ IStateMachineEngine::StringHandler OutputStateMachineEngine::ActionDcsDispatch(c
case DcsActionCodes::DECRSPS_RestorePresentationState:
handler = _dispatch->RestorePresentationState(parameters.at(0));
break;
case DcsActionCodes::TMUX_ControlEnter:
handler = _dispatch->EnterTmuxControl(parameters);
break;
default:
handler = nullptr;
break;

View File

@ -178,6 +178,7 @@ namespace Microsoft::Console::VirtualTerminal
DECRSTS_RestoreTerminalState = VTID("$p"),
DECRQSS_RequestSetting = VTID("$q"),
DECRSPS_RestorePresentationState = VTID("$t"),
TMUX_ControlEnter = VTID("p"),
};
enum Vt52ActionCodes : uint64_t