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

## Summary of the Pull Request
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.

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

## Detailed Description of the Pull Request / Additional comments
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).

## Validation Steps Performed
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.

## PR Checklist
- [x] Closes #19017
- [x] Tests added/passed
This commit is contained in:
James Holderness 2025-06-19 02:13:00 +01:00 committed by GitHub
parent 4cf492e36b
commit 00ee88400a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 42 additions and 19 deletions

View File

@ -94,7 +94,7 @@ void Terminal::UpdateSettings(ICoreSettings settings)
if (_stateMachine) if (_stateMachine)
{ {
SetVtChecksumReportSupport(settings.AllowVtChecksumReport()); SetOptionalFeatures(settings);
} }
_getTerminalInput().ForceDisableWin32InputMode(settings.ForceVTInput()); _getTerminalInput().ForceDisableWin32InputMode(settings.ForceVTInput());
@ -232,10 +232,13 @@ void Terminal::SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle)
engine.Dispatch().SetCursorStyle(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()); 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 bool Terminal::IsXtermBracketedPasteModeEnabled() const noexcept

View File

@ -95,7 +95,7 @@ public:
void SetHighContrastMode(bool hc) noexcept; void SetHighContrastMode(bool hc) noexcept;
void SetFontInfo(const FontInfo& fontInfo); void SetFontInfo(const FontInfo& fontInfo);
void SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle); 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; bool IsXtermBracketedPasteModeEnabled() const noexcept;
std::wstring_view GetWorkingDirectory() noexcept; std::wstring_view GetWorkingDirectory() noexcept;

View File

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

View File

@ -1297,11 +1297,6 @@ void AdaptDispatch::SelectAttributeChangeExtent(const DispatchTypes::ChangeExten
} }
} }
void AdaptDispatch::SetVtChecksumReportSupport(const bool enabled) noexcept
{
_vtChecksumReportEnabled = enabled;
}
// Routine Description: // Routine Description:
// - DECRQCRA - Computes and reports a checksum of the specified area of // - DECRQCRA - Computes and reports a checksum of the specified area of
// the buffer memory. // the buffer memory.
@ -1318,7 +1313,7 @@ void AdaptDispatch::RequestChecksumRectangularArea(const VTInt id, const VTInt p
// If this feature is not enabled, we'll just report a zero checksum. // If this feature is not enabled, we'll just report a zero checksum.
if constexpr (Feature_VtChecksumReport::IsEnabled()) 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 // 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. // of the pages, but we have no need for that, so we'll just return 0.
@ -1483,8 +1478,16 @@ void AdaptDispatch::DeviceAttributes()
// 28 = Rectangular area operations // 28 = Rectangular area operations
// 32 = Text macros // 32 = Text macros
// 42 = ISO Latin-2 character set // 42 = ISO Latin-2 character set
// 52 = Clipboard access
_ReturnCsiResponse(L"?61;4;6;7;14;21;22;23;24;28;32;42c"); if (_optionalFeatures.test(OptionalFeature::ClipboardWrite))
{
_ReturnCsiResponse(L"?61;4;6;7;14;21;22;23;24;28;32;42;52c");
}
else
{
_ReturnCsiResponse(L"?61;4;6;7;14;21;22;23;24;28;32;42c");
}
} }
// Routine Description: // Routine Description:
@ -4790,3 +4793,8 @@ void AdaptDispatch::PlaySounds(const VTParameters parameters)
_api.PlayMidiNote(noteNumber, noteNumber == 71 ? 0 : velocity, duration); _api.PlayMidiNote(noteNumber, noteNumber == 71 ? 0 : velocity, duration);
}); });
} }
void AdaptDispatch::SetOptionalFeatures(const til::enumset<OptionalFeature> features) noexcept
{
_optionalFeatures = features;
}

View File

@ -188,7 +188,7 @@ namespace Microsoft::Console::VirtualTerminal
void PlaySounds(const VTParameters parameters) override; // DECPS void PlaySounds(const VTParameters parameters) override; // DECPS
void SetVtChecksumReportSupport(const bool enabled) noexcept override; void SetOptionalFeatures(const til::enumset<OptionalFeature> features) noexcept override;
private: private:
enum class Mode enum class Mode
@ -313,7 +313,7 @@ namespace Microsoft::Console::VirtualTerminal
std::unique_ptr<FontBuffer> _fontBuffer; std::unique_ptr<FontBuffer> _fontBuffer;
std::shared_ptr<MacroBuffer> _macroBuffer; std::shared_ptr<MacroBuffer> _macroBuffer;
std::optional<unsigned int> _initialCodePage; 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 // 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 // one for the main buffer (at index 0), and another for the alt buffer

View File

@ -178,7 +178,7 @@ public:
void PlaySounds(const VTParameters /*parameters*/) override{}; // DECPS 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 #pragma warning(default : 26440) // Restore "can be declared noexcept" warning

View File

@ -415,7 +415,6 @@ public:
auto& renderer = _testGetSet->_renderer; auto& renderer = _testGetSet->_renderer;
auto& renderSettings = renderer._renderSettings; auto& renderSettings = renderer._renderSettings;
auto adapter = std::make_unique<AdaptDispatch>(*_testGetSet, &renderer, renderSettings, _terminalInput); auto adapter = std::make_unique<AdaptDispatch>(*_testGetSet, &renderer, renderSettings, _terminalInput);
adapter->SetVtChecksumReportSupport(true);
fSuccess = adapter.get() != nullptr; fSuccess = adapter.get() != nullptr;
if (fSuccess) if (fSuccess)
@ -1737,11 +1736,15 @@ public:
Log::Comment(L"Test 1: Verify normal response."); Log::Comment(L"Test 1: Verify normal response.");
_testGetSet->PrepData(); _testGetSet->PrepData();
_pDispatch->DeviceAttributes(); _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"; Log::Comment(L"Test 2: Verify response with clipboard disabled.");
_testGetSet->ValidateInputEvent(pwszExpectedResponse); _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->PrepData();
_testGetSet->_returnResponseResult = FALSE; _testGetSet->_returnResponseResult = FALSE;
@ -2187,6 +2190,8 @@ public:
using namespace std::string_view_literals; using namespace std::string_view_literals;
_pDispatch->SetOptionalFeatures(ITermDispatch::OptionalFeature::ChecksumReport);
Log::Comment(L"Test 1: ASCII characters"); Log::Comment(L"Test 1: ASCII characters");
outputText(L"A"sv); outputText(L"A"sv);
verifyChecksumReport(L"FF4F"); verifyChecksumReport(L"FF4F");