Indicate support for OSC 52 in the DA1 report (#19034)

Some applications that make use of the `OSC 52` clipboard sequence will
only do so if they can be certain that the terminal actually has that
functionality. Indicating our support for `OSC 52` in the `DA1` report
will give them an easy way to detect that.

`OSC 52` support was added to Windows Terminal in issue #5823, and to
ConHost in issue #18949.

Support for writing to the clipboard is indicated in the primary device
attributes report by the extension parameter `52`. This is obviously not
a standard DEC extension, but it's one that's been agreed upon by a
number of modern terminals. The extension is only reported when writing
to the clipboard is actually permitted (Windows Terminal has an option
to disable that).

I've updated the Device Attributes unit test to check that we're
reporting extension `52` when clipboard access is enabled, and not
reporting it when disabled.

- [x] Closes #19017
- [x] Tests added/passed

(cherry picked from commit 00ee88400aab0cd94409204ce0c6d20854ba7eff)
Service-Card-Id: PVTI_lADOAF3p4s4AmhmQzgbpe4k
Service-Version: 1.22
This commit is contained in:
James Holderness 2025-06-19 02:13:00 +01:00 committed by Dustin L. Howett
parent 43f800d043
commit 32e69d7163
7 changed files with 42 additions and 19 deletions

View File

@ -93,7 +93,7 @@ void Terminal::UpdateSettings(ICoreSettings settings)
if (_stateMachine)
{
SetVtChecksumReportSupport(settings.AllowVtChecksumReport());
SetOptionalFeatures(settings);
}
_getTerminalInput().ForceDisableWin32InputMode(settings.ForceVTInput());
@ -218,10 +218,13 @@ void Terminal::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
engine.Dispatch().SetCursorStyle(cursorStyle);
}
void Terminal::SetVtChecksumReportSupport(const bool enabled)
void Terminal::SetOptionalFeatures(winrt::Microsoft::Terminal::Core::ICoreSettings settings)
{
auto& engine = reinterpret_cast<OutputStateMachineEngine&>(_stateMachine->Engine());
engine.Dispatch().SetVtChecksumReportSupport(enabled);
auto features = til::enumset<ITermDispatch::OptionalFeature>{};
features.set(ITermDispatch::OptionalFeature::ChecksumReport, settings.AllowVtChecksumReport());
features.set(ITermDispatch::OptionalFeature::ClipboardWrite, settings.AllowVtClipboardWrite());
engine.Dispatch().SetOptionalFeatures(features);
}
bool Terminal::IsXtermBracketedPasteModeEnabled() const noexcept

View File

@ -94,7 +94,7 @@ public:
void UpdateAppearance(const winrt::Microsoft::Terminal::Core::ICoreAppearance& appearance);
void SetFontInfo(const FontInfo& fontInfo);
void SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle);
void SetVtChecksumReportSupport(const bool enabled);
void SetOptionalFeatures(winrt::Microsoft::Terminal::Core::ICoreSettings settings);
bool IsXtermBracketedPasteModeEnabled() const noexcept;
std::wstring_view GetWorkingDirectory() noexcept;

View File

@ -25,6 +25,12 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch
public:
using StringHandler = std::function<bool(const wchar_t)>;
enum class OptionalFeature
{
ChecksumReport,
ClipboardWrite,
};
#pragma warning(push)
#pragma warning(disable : 26432) // suppress rule of 5 violation on interface because tampering with this is fraught with peril
virtual ~ITermDispatch() = 0;
@ -132,7 +138,6 @@ public:
virtual void ScreenAlignmentPattern() = 0; // DECALN
virtual void SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) = 0; // DECSCUSR
virtual void SetVtChecksumReportSupport(const bool enabled) = 0;
virtual void SetClipboard(wil::zwstring_view content) = 0; // OSCSetClipboard
@ -184,6 +189,8 @@ public:
virtual StringHandler RestorePresentationState(const DispatchTypes::PresentationReportFormat format) = 0; // DECRSPS
virtual void PlaySounds(const VTParameters parameters) = 0; // DECPS
virtual void SetOptionalFeatures(const til::enumset<OptionalFeature> features) = 0;
};
inline Microsoft::Console::VirtualTerminal::ITermDispatch::~ITermDispatch() = default;
#pragma warning(pop)

View File

@ -1304,11 +1304,6 @@ void AdaptDispatch::SelectAttributeChangeExtent(const DispatchTypes::ChangeExten
}
}
void AdaptDispatch::SetVtChecksumReportSupport(const bool enabled) noexcept
{
_vtChecksumReportEnabled = enabled;
}
// Routine Description:
// - DECRQCRA - Computes and reports a checksum of the specified area of
// the buffer memory.
@ -1325,7 +1320,7 @@ void AdaptDispatch::RequestChecksumRectangularArea(const VTInt id, const VTInt p
// If this feature is not enabled, we'll just report a zero checksum.
if constexpr (Feature_VtChecksumReport::IsEnabled())
{
if (_vtChecksumReportEnabled)
if (_optionalFeatures.test(OptionalFeature::ChecksumReport))
{
// If the page number is 0, then we're meant to return a checksum of all
// of the pages, but we have no need for that, so we'll just return 0.
@ -1491,8 +1486,16 @@ void AdaptDispatch::DeviceAttributes()
// 28 = Rectangular area operations
// 32 = Text macros
// 42 = ISO Latin-2 character set
// 52 = Clipboard access
_api.ReturnResponse(L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42c");
if (_optionalFeatures.test(OptionalFeature::ClipboardWrite))
{
_api.ReturnResponse(L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42;52c");
}
else
{
_api.ReturnResponse(L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42c");
}
}
// Routine Description:
@ -4769,3 +4772,8 @@ void AdaptDispatch::PlaySounds(const VTParameters parameters)
_api.PlayMidiNote(noteNumber, noteNumber == 71 ? 0 : velocity, duration);
});
}
void AdaptDispatch::SetOptionalFeatures(const til::enumset<OptionalFeature> features) noexcept
{
_optionalFeatures = features;
}

View File

@ -187,7 +187,7 @@ namespace Microsoft::Console::VirtualTerminal
void PlaySounds(const VTParameters parameters) override; // DECPS
void SetVtChecksumReportSupport(const bool enabled) noexcept override;
void SetOptionalFeatures(const til::enumset<OptionalFeature> features) noexcept override;
private:
enum class Mode
@ -308,7 +308,7 @@ namespace Microsoft::Console::VirtualTerminal
std::unique_ptr<FontBuffer> _fontBuffer;
std::shared_ptr<MacroBuffer> _macroBuffer;
std::optional<unsigned int> _initialCodePage;
bool _vtChecksumReportEnabled = false;
til::enumset<OptionalFeature> _optionalFeatures = { OptionalFeature::ClipboardWrite };
// We have two instances of the saved cursor state, because we need
// one for the main buffer (at index 0), and another for the alt buffer

View File

@ -177,7 +177,7 @@ public:
void PlaySounds(const VTParameters /*parameters*/) override{}; // DECPS
void SetVtChecksumReportSupport(const bool /*enabled*/) override{};
void SetOptionalFeatures(const til::enumset<OptionalFeature> /*features*/) override{};
};
#pragma warning(default : 26440) // Restore "can be declared noexcept" warning

View File

@ -405,7 +405,6 @@ public:
auto& renderer = _testGetSet->_renderer;
auto& renderSettings = renderer._renderSettings;
auto adapter = std::make_unique<AdaptDispatch>(*_testGetSet, &renderer, renderSettings, _terminalInput);
adapter->SetVtChecksumReportSupport(true);
fSuccess = adapter.get() != nullptr;
if (fSuccess)
@ -1727,11 +1726,15 @@ public:
Log::Comment(L"Test 1: Verify normal response.");
_testGetSet->PrepData();
_pDispatch->DeviceAttributes();
_testGetSet->ValidateInputEvent(L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42;52c");
auto pwszExpectedResponse = L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42c";
_testGetSet->ValidateInputEvent(pwszExpectedResponse);
Log::Comment(L"Test 2: Verify response with clipboard disabled.");
_testGetSet->PrepData();
_pDispatch->SetOptionalFeatures({});
_pDispatch->DeviceAttributes();
_testGetSet->ValidateInputEvent(L"\x1b[?61;4;6;7;14;21;22;23;24;28;32;42c");
Log::Comment(L"Test 2: Verify failure when ReturnResponse doesn't work.");
Log::Comment(L"Test 3: Verify failure when ReturnResponse doesn't work.");
_testGetSet->PrepData();
_testGetSet->_returnResponseResult = FALSE;
@ -2177,6 +2180,8 @@ public:
using namespace std::string_view_literals;
_pDispatch->SetOptionalFeatures(ITermDispatch::OptionalFeature::ChecksumReport);
Log::Comment(L"Test 1: ASCII characters");
outputText(L"A"sv);
verifyChecksumReport(L"FF4F");