mirror of
https://github.com/microsoft/terminal.git
synced 2026-02-06 13:44:55 +00:00
Add support for the DECPS (Play Sound) escape sequence (#13208)
## Summary of the Pull Request The `DECPS` (Play Sound) escape sequence provides applications with a way to play a basic sequence of musical notes. This emulates functionality that was originally supported on the DEC VT520 and VT525 hardware terminals. ## PR Checklist * [x] Closes #8687 * [x] CLA signed. * [ ] Tests added/passed * [ ] Documentation updated. * [ ] Schema updated. * [x] I've discussed this with core contributors already. Issue number where discussion took place: #8687 ## Detailed Description of the Pull Request / Additional comments When a `DECPS` control is executed, any further output is blocked until all the notes have finished playing. So to prevent the UI from hanging during this period, we have to temporarily release the console/terminal lock, and then reacquire it before returning. The problem we then have is how to deal with the terminal being closed during that unlocked interval. The way I've dealt with that is with a promise that is set to indicate a shutdown. This immediately aborts any sound that is in progress, but also signals the thread that it needs to exit as soon as possible. The thread exit is achieved by throwing a custom exception which is recognised by the state machine and rethrown instead of being logged. This gets it all the way up to the root of the write operation, so it won't attempt to process anything further output that might still be buffered. ## Validation Steps Performed Thanks to the testing done by @jerch on a real VT525 terminal, we have a good idea of how this sequence is supposed to work, and I'm fairly confident that our implementation is reasonably compatible. The only significant difference I'm aware of is that we support multiple notes in a sequence. That was a feature that was documented in the VT520/VT525 manual, but didn't appear to be supported on the actual device.
This commit is contained in:
4
.github/actions/spelling/expect/expect.txt
vendored
4
.github/actions/spelling/expect/expect.txt
vendored
@@ -539,6 +539,7 @@ DECNRCM
|
||||
DECOM
|
||||
deconstructed
|
||||
DECPCTERM
|
||||
DECPS
|
||||
DECRC
|
||||
DECREQTPARM
|
||||
DECRLM
|
||||
@@ -632,6 +633,7 @@ dllmain
|
||||
DLLVERSIONINFO
|
||||
DLOAD
|
||||
DLOOK
|
||||
Dls
|
||||
dmp
|
||||
DOCTYPE
|
||||
docx
|
||||
@@ -1006,6 +1008,7 @@ HKLM
|
||||
hlocal
|
||||
hlsl
|
||||
HMENU
|
||||
HMIDIOUT
|
||||
hmod
|
||||
hmodule
|
||||
hmon
|
||||
@@ -1393,6 +1396,7 @@ MAKELANGID
|
||||
MAKELONG
|
||||
MAKELPARAM
|
||||
MAKELRESULT
|
||||
MAKEWORD
|
||||
malloc
|
||||
manpage
|
||||
MAPBITMAP
|
||||
|
||||
@@ -410,6 +410,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InteractivityOneCore", "src
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RendererWddmCon", "src\renderer\wddmcon\lib\wddmcon.vcxproj", "{75C6F576-18E9-4566-978A-F0A301CAC090}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Audio", "Audio", "{40BD8415-DD93-4200-8D82-498DDDC08CC8}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MidiAudio", "src\audio\midi\lib\midi.vcxproj", "{3C67784E-1453-49C2-9660-483E2CC7F7AD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
AuditMode|Any CPU = AuditMode|Any CPU
|
||||
@@ -3449,6 +3453,46 @@ Global
|
||||
{75C6F576-18E9-4566-978A-F0A301CAC090}.Release|x64.Build.0 = Release|x64
|
||||
{75C6F576-18E9-4566-978A-F0A301CAC090}.Release|x86.ActiveCfg = Release|Win32
|
||||
{75C6F576-18E9-4566-978A-F0A301CAC090}.Release|x86.Build.0 = Release|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|Any CPU.ActiveCfg = AuditMode|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|ARM.ActiveCfg = AuditMode|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|ARM64.ActiveCfg = AuditMode|ARM64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|ARM64.Build.0 = AuditMode|ARM64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|DotNet_x64Test.ActiveCfg = AuditMode|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|DotNet_x86Test.ActiveCfg = AuditMode|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|x64.ActiveCfg = AuditMode|x64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|x64.Build.0 = AuditMode|x64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|x86.ActiveCfg = AuditMode|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.AuditMode|x86.Build.0 = AuditMode|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|ARM.ActiveCfg = Debug|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|DotNet_x64Test.ActiveCfg = Debug|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|DotNet_x86Test.ActiveCfg = Debug|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|x64.Build.0 = Debug|x64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Debug|x86.Build.0 = Debug|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|Any CPU.ActiveCfg = Fuzzing|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|ARM.ActiveCfg = Fuzzing|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|ARM64.ActiveCfg = Fuzzing|ARM64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|ARM64.Build.0 = Fuzzing|ARM64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|DotNet_x64Test.ActiveCfg = Fuzzing|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|DotNet_x86Test.ActiveCfg = Fuzzing|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|x64.ActiveCfg = Fuzzing|x64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|x64.Build.0 = Fuzzing|x64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Fuzzing|x86.Build.0 = Fuzzing|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|ARM.ActiveCfg = Release|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|DotNet_x64Test.ActiveCfg = Release|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|DotNet_x86Test.ActiveCfg = Release|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|x64.ActiveCfg = Release|x64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|x64.Build.0 = Release|x64
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|x86.ActiveCfg = Release|Win32
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -3552,6 +3596,8 @@ Global
|
||||
{8222900C-8B6C-452A-91AC-BE95DB04B95F} = {05500DEF-2294-41E3-AF9A-24E580B82836}
|
||||
{06EC74CB-9A12-428C-B551-8537EC964726} = {E8F24881-5E37-4362-B191-A3BA0ED7F4EB}
|
||||
{75C6F576-18E9-4566-978A-F0A301CAC090} = {05500DEF-2294-41E3-AF9A-24E580B82836}
|
||||
{40BD8415-DD93-4200-8D82-498DDDC08CC8} = {89CDCC5C-9F53-4054-97A4-639D99F169CD}
|
||||
{3C67784E-1453-49C2-9660-483E2CC7F7AD} = {40BD8415-DD93-4200-8D82-498DDDC08CC8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3140B1B7-C8EE-43D1-A772-D82A7061A271}
|
||||
|
||||
116
src/audio/midi/MidiAudio.cpp
Normal file
116
src/audio/midi/MidiAudio.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "MidiAudio.hpp"
|
||||
#include "../terminal/parser/stateMachine.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
class MidiOut
|
||||
{
|
||||
public:
|
||||
static constexpr auto NOTE_OFF = 0x80;
|
||||
static constexpr auto NOTE_ON = 0x90;
|
||||
static constexpr auto PROGRAM_CHANGE = 0xC0;
|
||||
|
||||
// We're using a square wave as an approximation of the sound that the
|
||||
// original VT525 terminals might have produced. This is probably not
|
||||
// quite right, but it works reasonably well.
|
||||
static constexpr auto SQUARE_WAVE_SYNTH = 80;
|
||||
|
||||
MidiOut() noexcept
|
||||
{
|
||||
midiOutOpen(&handle, MIDI_MAPPER, NULL, NULL, CALLBACK_NULL);
|
||||
OutputMessage(PROGRAM_CHANGE, SQUARE_WAVE_SYNTH);
|
||||
}
|
||||
~MidiOut() noexcept
|
||||
{
|
||||
midiOutClose(handle);
|
||||
}
|
||||
void OutputMessage(const int b1, const int b2, const int b3 = 0, const int b4 = 0) noexcept
|
||||
{
|
||||
midiOutShortMsg(handle, MAKELONG(MAKEWORD(b1, b2), MAKEWORD(b3, b4)));
|
||||
}
|
||||
|
||||
MidiOut(const MidiOut&) = delete;
|
||||
MidiOut(MidiOut&&) = delete;
|
||||
MidiOut& operator=(const MidiOut&) = delete;
|
||||
MidiOut& operator=(MidiOut&&) = delete;
|
||||
|
||||
private:
|
||||
HMIDIOUT handle = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
MidiAudio::~MidiAudio() noexcept
|
||||
{
|
||||
try
|
||||
{
|
||||
#pragma warning(suppress : 26447)
|
||||
// We acquire the lock here so the class isn't destroyed while in use.
|
||||
// If this throws, we'll catch it, so the C26447 warning is bogus.
|
||||
_inUseMutex.lock();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If the lock fails, we'll just have to live with the consequences.
|
||||
}
|
||||
}
|
||||
|
||||
void MidiAudio::Initialize()
|
||||
{
|
||||
_shutdownFuture = _shutdownPromise.get_future();
|
||||
}
|
||||
|
||||
void MidiAudio::Shutdown()
|
||||
{
|
||||
// Once the shutdown promise is set, any note that is playing will stop
|
||||
// immediately, and the Unlock call will exit the thread ASAP.
|
||||
_shutdownPromise.set_value();
|
||||
}
|
||||
|
||||
void MidiAudio::Lock()
|
||||
{
|
||||
_inUseMutex.lock();
|
||||
}
|
||||
|
||||
void MidiAudio::Unlock()
|
||||
{
|
||||
// We need to check the shutdown status before releasing the mutex,
|
||||
// because after that the class could be destroyed.
|
||||
const auto shutdownStatus = _shutdownFuture.wait_for(0s);
|
||||
_inUseMutex.unlock();
|
||||
// If the wait didn't timeout, that means the shutdown promise was set,
|
||||
// so we need to exit the thread ASAP by throwing an exception.
|
||||
if (shutdownStatus != std::future_status::timeout)
|
||||
{
|
||||
throw Microsoft::Console::VirtualTerminal::StateMachine::ShutdownException{};
|
||||
}
|
||||
}
|
||||
|
||||
void MidiAudio::PlayNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) noexcept
|
||||
try
|
||||
{
|
||||
// The MidiOut is a local static because we can only have one instance,
|
||||
// and we only want to construct it when it's actually needed.
|
||||
static MidiOut midiOut;
|
||||
|
||||
if (velocity)
|
||||
{
|
||||
midiOut.OutputMessage(MidiOut::NOTE_ON, noteNumber, velocity);
|
||||
}
|
||||
|
||||
// By waiting on the shutdown future with the duration of the note, we'll
|
||||
// either be paused for the appropriate amount of time, or we'll break out
|
||||
// of the wait early if we've been shutdown.
|
||||
_shutdownFuture.wait_for(duration);
|
||||
|
||||
if (velocity)
|
||||
{
|
||||
midiOut.OutputMessage(MidiOut::NOTE_OFF, noteNumber, velocity);
|
||||
}
|
||||
}
|
||||
CATCH_LOG()
|
||||
36
src/audio/midi/MidiAudio.hpp
Normal file
36
src/audio/midi/MidiAudio.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- MidiAudio.hpp
|
||||
|
||||
Abstract:
|
||||
This modules provide basic MIDI support with blocking sound output.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
|
||||
class MidiAudio
|
||||
{
|
||||
public:
|
||||
MidiAudio() = default;
|
||||
MidiAudio(const MidiAudio&) = delete;
|
||||
MidiAudio(MidiAudio&&) = delete;
|
||||
MidiAudio& operator=(const MidiAudio&) = delete;
|
||||
MidiAudio& operator=(MidiAudio&&) = delete;
|
||||
~MidiAudio() noexcept;
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
void Lock();
|
||||
void Unlock();
|
||||
void PlayNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) noexcept;
|
||||
|
||||
private:
|
||||
std::promise<void> _shutdownPromise;
|
||||
std::future<void> _shutdownFuture;
|
||||
std::mutex _inUseMutex;
|
||||
};
|
||||
26
src/audio/midi/lib/midi.vcxproj
Normal file
26
src/audio/midi/lib/midi.vcxproj
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ProjectGuid>{3c67784e-1453-49c2-9660-483e2cc7f7ad}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>midi</RootNamespace>
|
||||
<ProjectName>MidiAudio</ProjectName>
|
||||
<TargetName>MidiAudio</TargetName>
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(SolutionDir)src\common.build.pre.props" />
|
||||
<Import Project="$(SolutionDir)src\common.nugetversions.props" />
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\MidiAudio.cpp" />
|
||||
<ClCompile Include="..\precomp.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\MidiAudio.hpp" />
|
||||
<ClInclude Include="..\precomp.h" />
|
||||
</ItemGroup>
|
||||
<!-- Careful reordering these. Some default props (contained in these files) are order sensitive. -->
|
||||
<Import Project="$(SolutionDir)src\common.build.post.props" />
|
||||
<Import Project="$(SolutionDir)src\common.nugetversions.targets" />
|
||||
</Project>
|
||||
4
src/audio/midi/precomp.cpp
Normal file
4
src/audio/midi/precomp.cpp
Normal file
@@ -0,0 +1,4 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
30
src/audio/midi/precomp.h
Normal file
30
src/audio/midi/precomp.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- precomp.h
|
||||
|
||||
Abstract:
|
||||
- Contains external headers to include in the precompile phase of console build process.
|
||||
- Avoid including internal project headers. Instead include them only in the classes that need them (helps with test project building).
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// clang-format off
|
||||
|
||||
// This includes support libraries from the CRT, STL, WIL, and GSL
|
||||
#include "LibraryIncludes.h"
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
|
||||
#define NOMCX
|
||||
#define NOHELP
|
||||
#define NOCOMM
|
||||
#endif
|
||||
|
||||
// Windows Header Files:
|
||||
#include <windows.h>
|
||||
|
||||
// clang-format on
|
||||
@@ -105,6 +105,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
auto pfnShowWindowChanged = std::bind(&ControlCore::_terminalShowWindowChanged, this, std::placeholders::_1);
|
||||
_terminal->SetShowWindowCallback(pfnShowWindowChanged);
|
||||
|
||||
auto pfnPlayMidiNote = std::bind(&ControlCore::_terminalPlayMidiNote, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
|
||||
_terminal->SetPlayMidiNoteCallback(pfnPlayMidiNote);
|
||||
|
||||
// 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
|
||||
@@ -201,6 +204,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
{
|
||||
_renderer->TriggerTeardown();
|
||||
}
|
||||
|
||||
_shutdownMidiAudio();
|
||||
}
|
||||
|
||||
bool ControlCore::Initialize(const double actualWidth,
|
||||
@@ -1223,6 +1228,66 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Plays a single MIDI note, blocking for the duration.
|
||||
// Arguments:
|
||||
// - noteNumber - The MIDI note number to be played (0 - 127).
|
||||
// - velocity - The force with which the note should be played (0 - 127).
|
||||
// - duration - How long the note should be sustained (in microseconds).
|
||||
void ControlCore::_terminalPlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration)
|
||||
{
|
||||
// We create the audio instance on demand, and lock it for the duration
|
||||
// of the note output so it can't be destroyed while in use.
|
||||
auto& midiAudio = _getMidiAudio();
|
||||
midiAudio.Lock();
|
||||
|
||||
// We then unlock the terminal, so the UI doesn't hang while we're busy.
|
||||
auto& terminalLock = _terminal->GetReadWriteLock();
|
||||
terminalLock.unlock();
|
||||
|
||||
// This call will block for the duration, unless shutdown early.
|
||||
midiAudio.PlayNote(noteNumber, velocity, duration);
|
||||
|
||||
// Once complete, we reacquire the terminal lock and unlock the audio.
|
||||
// If the terminal has shutdown in the meantime, the Unlock call
|
||||
// will throw an exception, forcing the thread to exit ASAP.
|
||||
terminalLock.lock();
|
||||
midiAudio.Unlock();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns the MIDI audio instance, created on demand.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a reference to the MidiAudio instance.
|
||||
MidiAudio& ControlCore::_getMidiAudio()
|
||||
{
|
||||
if (!_midiAudio)
|
||||
{
|
||||
_midiAudio = std::make_unique<MidiAudio>();
|
||||
_midiAudio->Initialize();
|
||||
}
|
||||
return *_midiAudio;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Shuts down the MIDI audio system if previously instantiated.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void ControlCore::_shutdownMidiAudio()
|
||||
{
|
||||
if (_midiAudio)
|
||||
{
|
||||
// We lock the terminal here to make sure the shutdown promise is
|
||||
// set before the audio is unlocked in the thread that is playing.
|
||||
auto lock = _terminal->LockForWriting();
|
||||
_midiAudio->Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
bool ControlCore::HasSelection() const
|
||||
{
|
||||
return _terminal->IsSelectionActive();
|
||||
@@ -1513,10 +1578,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
void ControlCore::_connectionOutputHandler(const hstring& hstr)
|
||||
{
|
||||
_terminal->Write(hstr);
|
||||
try
|
||||
{
|
||||
_terminal->Write(hstr);
|
||||
|
||||
// Start the throttled update of where our hyperlinks are.
|
||||
_updatePatternLocations->Run();
|
||||
// Start the throttled update of where our hyperlinks are.
|
||||
_updatePatternLocations->Run();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// We're expecting to receive an exception here if the terminal
|
||||
// is closed while we're blocked playing a MIDI note.
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include "ControlCore.g.h"
|
||||
#include "ControlSettings.h"
|
||||
#include "../../audio/midi/MidiAudio.hpp"
|
||||
#include "../../renderer/base/Renderer.hpp"
|
||||
#include "../../cascadia/TerminalCore/Terminal.hpp"
|
||||
#include "../buffer/out/search.h"
|
||||
@@ -273,8 +274,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void _terminalCursorPositionChanged();
|
||||
void _terminalTaskbarProgressChanged();
|
||||
void _terminalShowWindowChanged(bool showOrHide);
|
||||
void _terminalPlayMidiNote(const int noteNumber,
|
||||
const int velocity,
|
||||
const std::chrono::microseconds duration);
|
||||
#pragma endregion
|
||||
|
||||
std::unique_ptr<MidiAudio> _midiAudio;
|
||||
|
||||
MidiAudio& _getMidiAudio();
|
||||
void _shutdownMidiAudio();
|
||||
|
||||
#pragma region RendererCallbacks
|
||||
void _rendererWarning(const HRESULT hr);
|
||||
void _renderEngineSwapChainChanged();
|
||||
|
||||
@@ -147,9 +147,13 @@
|
||||
<PRIResource Include="Resources\en-US\Resources.resw" />
|
||||
<OCResourceDirectory Include="Resources" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<SDKReference Include="Microsoft.Midi.GmDls, Version=10.0.22000.0" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Project References ======================== -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\types\lib\types.vcxproj" />
|
||||
<ProjectReference Include="..\..\audio\midi\lib\midi.vcxproj" />
|
||||
<ProjectReference Include="..\..\buffer\out\lib\bufferout.vcxproj" />
|
||||
<ProjectReference Include="$(OpenConsoleDir)src\renderer\base\lib\base.vcxproj" />
|
||||
<ProjectReference Include="..\..\renderer\atlas\atlas.vcxproj" />
|
||||
|
||||
@@ -963,6 +963,15 @@ WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept
|
||||
#endif
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get a reference to the the terminal's read/write lock.
|
||||
// Return Value:
|
||||
// - a ticket_lock which can be used to manually lock or unlock the terminal.
|
||||
til::ticket_lock& Terminal::GetReadWriteLock() noexcept
|
||||
{
|
||||
return _readWriteLock;
|
||||
}
|
||||
|
||||
Viewport Terminal::_GetMutableViewport() const noexcept
|
||||
{
|
||||
// GH#3493: if we're in the alt buffer, then it's possible that the mutable
|
||||
@@ -1302,6 +1311,15 @@ void Terminal::SetShowWindowCallback(std::function<void(bool)> pfn) noexcept
|
||||
_pfnShowWindowChanged.swap(pfn);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Allows setting a callback for playing MIDI notes.
|
||||
// Arguments:
|
||||
// - pfn: a function callback that takes a note number, a velocity level, and a duration
|
||||
void Terminal::SetPlayMidiNoteCallback(std::function<void(const int, const int, const std::chrono::microseconds)> pfn) noexcept
|
||||
{
|
||||
_pfnPlayMidiNote.swap(pfn);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets the cursor to be currently on. On/Off is tracked independently of
|
||||
// cursor visibility (hidden/visible). On/off is controlled by the cursor
|
||||
|
||||
@@ -92,6 +92,7 @@ public:
|
||||
|
||||
[[nodiscard]] std::unique_lock<til::ticket_lock> LockForReading();
|
||||
[[nodiscard]] std::unique_lock<til::ticket_lock> LockForWriting();
|
||||
til::ticket_lock& GetReadWriteLock() noexcept;
|
||||
|
||||
short GetBufferHeight() const noexcept;
|
||||
|
||||
@@ -124,6 +125,7 @@ public:
|
||||
void CopyToClipboard(std::wstring_view content) override;
|
||||
void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override;
|
||||
void SetWorkingDirectory(std::wstring_view uri) override;
|
||||
void PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) override;
|
||||
void ShowWindow(bool showOrHide) override;
|
||||
void UseAlternateScreenBuffer() override;
|
||||
void UseMainScreenBuffer() override;
|
||||
@@ -201,6 +203,7 @@ public:
|
||||
void SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept;
|
||||
void TaskbarProgressChangedCallback(std::function<void()> pfn) noexcept;
|
||||
void SetShowWindowCallback(std::function<void(bool)> pfn) noexcept;
|
||||
void SetPlayMidiNoteCallback(std::function<void(const int, const int, const std::chrono::microseconds)> pfn) noexcept;
|
||||
|
||||
void SetCursorOn(const bool isOn);
|
||||
bool IsCursorBlinkingAllowed() const noexcept;
|
||||
@@ -270,6 +273,7 @@ private:
|
||||
std::function<void()> _pfnCursorPositionChanged;
|
||||
std::function<void()> _pfnTaskbarProgressChanged;
|
||||
std::function<void(bool)> _pfnShowWindowChanged;
|
||||
std::function<void(const int, const int, const std::chrono::microseconds)> _pfnPlayMidiNote;
|
||||
|
||||
RenderSettings _renderSettings;
|
||||
std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;
|
||||
|
||||
@@ -188,6 +188,11 @@ void Terminal::SetWorkingDirectory(std::wstring_view uri)
|
||||
_workingDirectory = uri;
|
||||
}
|
||||
|
||||
void Terminal::PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration)
|
||||
{
|
||||
_pfnPlayMidiNote(noteNumber, velocity, duration);
|
||||
}
|
||||
|
||||
void Terminal::UseAlternateScreenBuffer()
|
||||
{
|
||||
// the new alt buffer is exactly the size of the viewport.
|
||||
|
||||
@@ -372,6 +372,40 @@ Microsoft::Console::CursorBlinker& CONSOLE_INFORMATION::GetCursorBlinker() noexc
|
||||
return _blinker;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns the MIDI audio instance, created on demand.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - a reference to the MidiAudio instance.
|
||||
MidiAudio& CONSOLE_INFORMATION::GetMidiAudio()
|
||||
{
|
||||
if (!_midiAudio)
|
||||
{
|
||||
_midiAudio = std::make_unique<MidiAudio>();
|
||||
_midiAudio->Initialize();
|
||||
}
|
||||
return *_midiAudio;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Shuts down the MIDI audio system if previously instantiated.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CONSOLE_INFORMATION::ShutdownMidiAudio()
|
||||
{
|
||||
if (_midiAudio)
|
||||
{
|
||||
// We lock the console here to make sure the shutdown promise is
|
||||
// set before the audio is unlocked in the thread that is playing.
|
||||
LockConsole();
|
||||
_midiAudio->Shutdown();
|
||||
UnlockConsole();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Generates a CHAR_INFO for this output cell, using the TextAttribute
|
||||
// GetLegacyAttributes method to generate the legacy style attributes.
|
||||
|
||||
@@ -118,6 +118,11 @@
|
||||
<ClInclude Include="..\_output.h" />
|
||||
<ClInclude Include="..\_stream.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\audio\midi\lib\midi.vcxproj">
|
||||
<Project>{3c67784e-1453-49c2-9660-483e2cc7f7ad}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(IntDir)..\OpenConsoleProxy;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
|
||||
@@ -499,7 +499,7 @@ void SetActiveScreenBuffer(SCREEN_INFORMATION& screenInfo)
|
||||
// TODO: MSFT 9450717 This should join the ProcessList class when CtrlEvents become moved into the server. https://osgvsowi/9450717
|
||||
void CloseConsoleProcessState()
|
||||
{
|
||||
const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
// If there are no connected processes, sending control events is pointless as there's no one do send them to. In
|
||||
// this case we'll just exit conhost.
|
||||
|
||||
@@ -512,6 +512,8 @@ void CloseConsoleProcessState()
|
||||
|
||||
HandleCtrlEvent(CTRL_CLOSE_EVENT);
|
||||
|
||||
gci.ShutdownMidiAudio();
|
||||
|
||||
// Jiggle the handle: (see MSFT:19419231)
|
||||
// When we call this function, we'll only actually close the console once
|
||||
// we're totally unlocked. If our caller has the console locked, great,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "_stream.h"
|
||||
#include "getset.h"
|
||||
#include "handle.h"
|
||||
#include "directio.h"
|
||||
#include "output.h"
|
||||
|
||||
@@ -355,6 +356,34 @@ void ConhostInternalGetSet::SetWorkingDirectory(const std::wstring_view /*uri*/)
|
||||
{
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Plays a single MIDI note, blocking for the duration.
|
||||
// Arguments:
|
||||
// - noteNumber - The MIDI note number to be played (0 - 127).
|
||||
// - velocity - The force with which the note should be played (0 - 127).
|
||||
// - duration - How long the note should be sustained (in milliseconds).
|
||||
// Return value:
|
||||
// - true if successful. false otherwise.
|
||||
void ConhostInternalGetSet::PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration)
|
||||
{
|
||||
// We create the audio instance on demand, and lock it for the duration
|
||||
// of the note output so it can't be destroyed while in use.
|
||||
auto& midiAudio = ServiceLocator::LocateGlobals().getConsoleInformation().GetMidiAudio();
|
||||
midiAudio.Lock();
|
||||
|
||||
// We then unlock the console, so the UI doesn't hang while we're busy.
|
||||
UnlockConsole();
|
||||
|
||||
// This call will block for the duration, unless shutdown early.
|
||||
midiAudio.PlayNote(noteNumber, velocity, duration);
|
||||
|
||||
// Once complete, we reacquire the console lock and unlock the audio.
|
||||
// If the console has shutdown in the meantime, the Unlock call
|
||||
// will throw an exception, forcing the thread to exit ASAP.
|
||||
LockConsole();
|
||||
midiAudio.Unlock();
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Resizes the window to the specified dimensions, in characters.
|
||||
// Arguments:
|
||||
|
||||
@@ -67,6 +67,7 @@ public:
|
||||
void CopyToClipboard(const std::wstring_view content) override;
|
||||
void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override;
|
||||
void SetWorkingDirectory(const std::wstring_view uri) override;
|
||||
void PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) override;
|
||||
|
||||
bool IsConsolePty() const override;
|
||||
bool IsVtInputEnabled() const override;
|
||||
|
||||
@@ -28,6 +28,7 @@ Revision History:
|
||||
#include "../server/WaitQueue.h"
|
||||
|
||||
#include "../host/RenderData.hpp"
|
||||
#include "../audio/midi/MidiAudio.hpp"
|
||||
|
||||
// clang-format off
|
||||
// Flags flags
|
||||
@@ -138,6 +139,9 @@ public:
|
||||
friend class CommonState;
|
||||
Microsoft::Console::CursorBlinker& GetCursorBlinker() noexcept;
|
||||
|
||||
MidiAudio& GetMidiAudio();
|
||||
void ShutdownMidiAudio();
|
||||
|
||||
CHAR_INFO AsCharInfo(const OutputCellView& cell) const noexcept;
|
||||
|
||||
RenderData renderData;
|
||||
@@ -153,6 +157,7 @@ private:
|
||||
|
||||
Microsoft::Console::VirtualTerminal::VtIo _vtIo;
|
||||
Microsoft::Console::CursorBlinker _blinker;
|
||||
std::unique_ptr<MidiAudio> _midiAudio;
|
||||
};
|
||||
|
||||
#define ConsoleLocked() (ServiceLocator::LocateGlobals()->getConsoleInformation()->ConsoleLock.OwningThread == NtCurrentTeb()->ClientId.UniqueThread)
|
||||
|
||||
@@ -145,6 +145,8 @@ public:
|
||||
const DispatchTypes::DrcsCharsetSize charsetSize) = 0; // DECDLD
|
||||
|
||||
virtual StringHandler RequestSetting() = 0; // DECRQSS
|
||||
|
||||
virtual bool PlaySounds(const VTParameters parameters) = 0; // DECPS
|
||||
};
|
||||
inline Microsoft::Console::VirtualTerminal::ITermDispatch::~ITermDispatch() {}
|
||||
#pragma warning(pop)
|
||||
|
||||
@@ -70,6 +70,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
virtual void CopyToClipboard(const std::wstring_view content) = 0;
|
||||
virtual void SetTaskbarProgress(const DispatchTypes::TaskbarState state, const size_t progress) = 0;
|
||||
virtual void SetWorkingDirectory(const std::wstring_view uri) = 0;
|
||||
virtual void PlayMidiNote(const int noteNumber, const int velocity, const std::chrono::microseconds duration) = 0;
|
||||
|
||||
virtual bool ResizeWindow(const size_t width, const size_t height) = 0;
|
||||
virtual bool IsConsolePty() const = 0;
|
||||
|
||||
@@ -2686,3 +2686,39 @@ void AdaptDispatch::_ReportDECSTBMSetting()
|
||||
response.append(L"r\033\\"sv);
|
||||
_api.ReturnResponse({ response.data(), response.size() });
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - DECPS - Plays a sequence of musical notes.
|
||||
// Arguments:
|
||||
// - params - The volume, duration, and note values to play.
|
||||
// Return value:
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::PlaySounds(const VTParameters parameters)
|
||||
{
|
||||
// If we're a conpty, we return false so the command will be passed on
|
||||
// to the connected terminal. But we need to flush the current frame
|
||||
// first, otherwise the visual output will lag behind the sound.
|
||||
if (_api.IsConsolePty())
|
||||
{
|
||||
_renderer.TriggerFlush(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// First parameter is the volume, in the range 0 to 7. We multiply by
|
||||
// 127 / 7 to obtain an equivalent MIDI velocity in the range 0 to 127.
|
||||
const auto velocity = std::min(parameters.at(0).value_or(0), 7) * 127 / 7;
|
||||
// Second parameter is the duration, in the range 0 to 255. Units are
|
||||
// 1/32 of a second, so we multiply by 1000000us/32 to obtain microseconds.
|
||||
using namespace std::chrono_literals;
|
||||
const auto duration = std::min(parameters.at(1).value_or(0), 255) * 1000000us / 32;
|
||||
// The subsequent parameters are notes, in the range 0 to 25.
|
||||
return parameters.subspan(2).for_each([=](const auto param) {
|
||||
// Values 1 to 25 represent the notes C5 to C7, so we add 71 to
|
||||
// obtain the equivalent MIDI note numbers (72 = C5).
|
||||
const auto noteNumber = std::min(param.value_or(0), 25) + 71;
|
||||
// But value 0 is meant to be silent, so if the note number is 71,
|
||||
// we set the velocity to 0 (i.e. no volume).
|
||||
_api.PlayMidiNote(noteNumber, noteNumber == 71 ? 0 : velocity, duration);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -141,6 +141,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
StringHandler RequestSetting() override; // DECRQSS
|
||||
|
||||
bool PlaySounds(const VTParameters parameters) override; // DECPS
|
||||
|
||||
private:
|
||||
enum class ScrollDirection
|
||||
{
|
||||
|
||||
@@ -138,6 +138,8 @@ public:
|
||||
const DispatchTypes::DrcsCharsetSize /*charsetSize*/) override { return nullptr; }
|
||||
|
||||
StringHandler RequestSetting() override { return nullptr; }; // DECRQSS
|
||||
|
||||
bool PlaySounds(const VTParameters /*parameters*/) override { return false; }; // DECPS
|
||||
};
|
||||
|
||||
#pragma warning(default : 26440) // Restore "can be declared noexcept" warning
|
||||
|
||||
@@ -221,6 +221,11 @@ public:
|
||||
Log::Comment(L"SetWorkingDirectory MOCK called...");
|
||||
}
|
||||
|
||||
void PlayMidiNote(const int /*noteNumber*/, const int /*velocity*/, const std::chrono::microseconds /*duration*/) override
|
||||
{
|
||||
Log::Comment(L"PlayMidiNote MOCK called...");
|
||||
}
|
||||
|
||||
bool IsConsolePty() const override
|
||||
{
|
||||
Log::Comment(L"IsConsolePty MOCK called...");
|
||||
|
||||
@@ -620,6 +620,10 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
|
||||
success = _dispatch->AssignColor(parameters.at(0), parameters.at(1).value_or(0), parameters.at(2).value_or(0));
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECAC);
|
||||
break;
|
||||
case CsiActionCodes::DECPS_PlaySound:
|
||||
success = _dispatch->PlaySounds(parameters);
|
||||
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECPS);
|
||||
break;
|
||||
default:
|
||||
// If no functions to call, overall dispatch was a failure.
|
||||
success = false;
|
||||
|
||||
@@ -142,7 +142,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
XT_PushSgr = VTID("#{"),
|
||||
XT_PopSgr = VTID("#}"),
|
||||
DECSCPP_SetColumnsPerPage = VTID("$|"),
|
||||
DECAC_AssignColor = VTID(",|")
|
||||
DECAC_AssignColor = VTID(",|"),
|
||||
DECPS_PlaySound = VTID(",~")
|
||||
};
|
||||
|
||||
enum DcsActionCodes : uint64_t
|
||||
|
||||
@@ -385,7 +385,7 @@ static constexpr bool _isActionableFromGround(const wchar_t wch) noexcept
|
||||
// - wch - Character to dispatch.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_ActionExecute(const wchar_t wch) noexcept
|
||||
void StateMachine::_ActionExecute(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnExecute(wch);
|
||||
_trace.DispatchSequenceTrace(_SafeExecute([=]() {
|
||||
@@ -401,7 +401,7 @@ void StateMachine::_ActionExecute(const wchar_t wch) noexcept
|
||||
// - wch - Character to dispatch.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_ActionExecuteFromEscape(const wchar_t wch) noexcept
|
||||
void StateMachine::_ActionExecuteFromEscape(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnExecuteFromEscape(wch);
|
||||
_trace.DispatchSequenceTrace(_SafeExecute([=]() {
|
||||
@@ -415,7 +415,7 @@ void StateMachine::_ActionExecuteFromEscape(const wchar_t wch) noexcept
|
||||
// - wch - Character to dispatch.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_ActionPrint(const wchar_t wch) noexcept
|
||||
void StateMachine::_ActionPrint(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnAction(L"Print");
|
||||
_trace.DispatchSequenceTrace(_SafeExecute([=]() {
|
||||
@@ -444,7 +444,7 @@ void StateMachine::_ActionPrintString(const std::wstring_view string)
|
||||
// - wch - Character to dispatch.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_ActionEscDispatch(const wchar_t wch) noexcept
|
||||
void StateMachine::_ActionEscDispatch(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnAction(L"EscDispatch");
|
||||
_trace.DispatchSequenceTrace(_SafeExecuteWithLog(wch, [=]() {
|
||||
@@ -459,7 +459,7 @@ void StateMachine::_ActionEscDispatch(const wchar_t wch) noexcept
|
||||
// - wch - Character to dispatch.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_ActionVt52EscDispatch(const wchar_t wch) noexcept
|
||||
void StateMachine::_ActionVt52EscDispatch(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnAction(L"Vt52EscDispatch");
|
||||
_trace.DispatchSequenceTrace(_SafeExecuteWithLog(wch, [=]() {
|
||||
@@ -474,7 +474,7 @@ void StateMachine::_ActionVt52EscDispatch(const wchar_t wch) noexcept
|
||||
// - wch - Character to dispatch.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_ActionCsiDispatch(const wchar_t wch) noexcept
|
||||
void StateMachine::_ActionCsiDispatch(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnAction(L"CsiDispatch");
|
||||
_trace.DispatchSequenceTrace(_SafeExecuteWithLog(wch, [=]() {
|
||||
@@ -632,7 +632,7 @@ void StateMachine::_ActionOscPut(const wchar_t wch)
|
||||
// - wch - Character to dispatch.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_ActionOscDispatch(const wchar_t wch) noexcept
|
||||
void StateMachine::_ActionOscDispatch(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnAction(L"OscDispatch");
|
||||
_trace.DispatchSequenceTrace(_SafeExecuteWithLog(wch, [=]() {
|
||||
@@ -647,7 +647,7 @@ void StateMachine::_ActionOscDispatch(const wchar_t wch) noexcept
|
||||
// - wch - Character to dispatch.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_ActionSs3Dispatch(const wchar_t wch) noexcept
|
||||
void StateMachine::_ActionSs3Dispatch(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnAction(L"Ss3Dispatch");
|
||||
_trace.DispatchSequenceTrace(_SafeExecuteWithLog(wch, [=]() {
|
||||
@@ -662,7 +662,7 @@ void StateMachine::_ActionSs3Dispatch(const wchar_t wch) noexcept
|
||||
// - wch - Character to dispatch.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_ActionDcsDispatch(const wchar_t wch) noexcept
|
||||
void StateMachine::_ActionDcsDispatch(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnAction(L"DcsDispatch");
|
||||
|
||||
@@ -978,7 +978,7 @@ void StateMachine::_EnterSosPmApcString() noexcept
|
||||
// - wch - Character that triggered the event
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_EventGround(const wchar_t wch) noexcept
|
||||
void StateMachine::_EventGround(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnEvent(L"Ground");
|
||||
if (_isC0Code(wch) || _isDelete(wch))
|
||||
@@ -1093,7 +1093,7 @@ void StateMachine::_EventEscape(const wchar_t wch)
|
||||
// - wch - Character that triggered the event
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_EventEscapeIntermediate(const wchar_t wch) noexcept
|
||||
void StateMachine::_EventEscapeIntermediate(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnEvent(L"EscapeIntermediate");
|
||||
if (_isC0Code(wch))
|
||||
@@ -1187,7 +1187,7 @@ void StateMachine::_EventCsiEntry(const wchar_t wch)
|
||||
// - wch - Character that triggered the event
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_EventCsiIntermediate(const wchar_t wch) noexcept
|
||||
void StateMachine::_EventCsiIntermediate(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnEvent(L"CsiIntermediate");
|
||||
if (_isC0Code(wch))
|
||||
@@ -1225,7 +1225,7 @@ void StateMachine::_EventCsiIntermediate(const wchar_t wch) noexcept
|
||||
// - wch - Character that triggered the event
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_EventCsiIgnore(const wchar_t wch) noexcept
|
||||
void StateMachine::_EventCsiIgnore(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnEvent(L"CsiIgnore");
|
||||
if (_isC0Code(wch))
|
||||
@@ -1574,7 +1574,7 @@ void StateMachine::_EventDcsIgnore() noexcept
|
||||
// - wch - Character that triggered the event
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void StateMachine::_EventDcsIntermediate(const wchar_t wch) noexcept
|
||||
void StateMachine::_EventDcsIntermediate(const wchar_t wch)
|
||||
{
|
||||
_trace.TraceOnEvent(L"DcsIntermediate");
|
||||
if (_isC0Code(wch))
|
||||
@@ -1786,7 +1786,7 @@ void StateMachine::ProcessCharacter(const wchar_t wch)
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true if the engine successfully handled the string.
|
||||
bool StateMachine::FlushToTerminal() noexcept
|
||||
bool StateMachine::FlushToTerminal()
|
||||
{
|
||||
auto success{ true };
|
||||
|
||||
@@ -2012,11 +2012,15 @@ void StateMachine::_AccumulateTo(const wchar_t wch, VTInt& value) noexcept
|
||||
}
|
||||
|
||||
template<typename TLambda>
|
||||
bool StateMachine::_SafeExecute(TLambda&& lambda) noexcept
|
||||
bool StateMachine::_SafeExecute(TLambda&& lambda)
|
||||
try
|
||||
{
|
||||
return lambda();
|
||||
}
|
||||
catch (const ShutdownException&)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_HR(wil::ResultFromCaughtException());
|
||||
@@ -2024,7 +2028,7 @@ catch (...)
|
||||
}
|
||||
|
||||
template<typename TLambda>
|
||||
bool StateMachine::_SafeExecuteWithLog(const wchar_t wch, TLambda&& lambda) noexcept
|
||||
bool StateMachine::_SafeExecuteWithLog(const wchar_t wch, TLambda&& lambda)
|
||||
{
|
||||
const bool success = _SafeExecute(std::forward<TLambda>(lambda));
|
||||
if (!success)
|
||||
|
||||
@@ -61,26 +61,33 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
void ResetState() noexcept;
|
||||
|
||||
bool FlushToTerminal() noexcept;
|
||||
bool FlushToTerminal();
|
||||
|
||||
const IStateMachineEngine& Engine() const noexcept;
|
||||
IStateMachineEngine& Engine() noexcept;
|
||||
|
||||
class ShutdownException : public wil::ResultException
|
||||
{
|
||||
public:
|
||||
ShutdownException() noexcept :
|
||||
ResultException(E_ABORT) {}
|
||||
};
|
||||
|
||||
private:
|
||||
void _ActionExecute(const wchar_t wch) noexcept;
|
||||
void _ActionExecuteFromEscape(const wchar_t wch) noexcept;
|
||||
void _ActionPrint(const wchar_t wch) noexcept;
|
||||
void _ActionExecute(const wchar_t wch);
|
||||
void _ActionExecuteFromEscape(const wchar_t wch);
|
||||
void _ActionPrint(const wchar_t wch);
|
||||
void _ActionPrintString(const std::wstring_view string);
|
||||
void _ActionEscDispatch(const wchar_t wch) noexcept;
|
||||
void _ActionVt52EscDispatch(const wchar_t wch) noexcept;
|
||||
void _ActionEscDispatch(const wchar_t wch);
|
||||
void _ActionVt52EscDispatch(const wchar_t wch);
|
||||
void _ActionCollect(const wchar_t wch) noexcept;
|
||||
void _ActionParam(const wchar_t wch);
|
||||
void _ActionCsiDispatch(const wchar_t wch) noexcept;
|
||||
void _ActionCsiDispatch(const wchar_t wch);
|
||||
void _ActionOscParam(const wchar_t wch) noexcept;
|
||||
void _ActionOscPut(const wchar_t wch);
|
||||
void _ActionOscDispatch(const wchar_t wch) noexcept;
|
||||
void _ActionSs3Dispatch(const wchar_t wch) noexcept;
|
||||
void _ActionDcsDispatch(const wchar_t wch) noexcept;
|
||||
void _ActionOscDispatch(const wchar_t wch);
|
||||
void _ActionSs3Dispatch(const wchar_t wch);
|
||||
void _ActionDcsDispatch(const wchar_t wch);
|
||||
|
||||
void _ActionClear();
|
||||
void _ActionIgnore() noexcept;
|
||||
@@ -106,12 +113,12 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void _EnterDcsPassThrough() noexcept;
|
||||
void _EnterSosPmApcString() noexcept;
|
||||
|
||||
void _EventGround(const wchar_t wch) noexcept;
|
||||
void _EventGround(const wchar_t wch);
|
||||
void _EventEscape(const wchar_t wch);
|
||||
void _EventEscapeIntermediate(const wchar_t wch) noexcept;
|
||||
void _EventEscapeIntermediate(const wchar_t wch);
|
||||
void _EventCsiEntry(const wchar_t wch);
|
||||
void _EventCsiIntermediate(const wchar_t wch) noexcept;
|
||||
void _EventCsiIgnore(const wchar_t wch) noexcept;
|
||||
void _EventCsiIntermediate(const wchar_t wch);
|
||||
void _EventCsiIgnore(const wchar_t wch);
|
||||
void _EventCsiParam(const wchar_t wch);
|
||||
void _EventOscParam(const wchar_t wch) noexcept;
|
||||
void _EventOscString(const wchar_t wch);
|
||||
@@ -121,7 +128,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void _EventVt52Param(const wchar_t wch);
|
||||
void _EventDcsEntry(const wchar_t wch);
|
||||
void _EventDcsIgnore() noexcept;
|
||||
void _EventDcsIntermediate(const wchar_t wch) noexcept;
|
||||
void _EventDcsIntermediate(const wchar_t wch);
|
||||
void _EventDcsParam(const wchar_t wch);
|
||||
void _EventDcsPassThrough(const wchar_t wch);
|
||||
void _EventSosPmApcString(const wchar_t wch) noexcept;
|
||||
@@ -129,9 +136,9 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
void _AccumulateTo(const wchar_t wch, VTInt& value) noexcept;
|
||||
|
||||
template<typename TLambda>
|
||||
bool _SafeExecute(TLambda&& lambda) noexcept;
|
||||
bool _SafeExecute(TLambda&& lambda);
|
||||
template<typename TLambda>
|
||||
bool _SafeExecuteWithLog(const wchar_t wch, TLambda&& lambda) noexcept;
|
||||
bool _SafeExecuteWithLog(const wchar_t wch, TLambda&& lambda);
|
||||
|
||||
enum class VTStates
|
||||
{
|
||||
|
||||
@@ -281,6 +281,7 @@ void TermTelemetry::WriteFinalTraceLog() const
|
||||
TraceLoggingUInt32(_uiTimesUsed[XTPUSHSGR], "XTPUSHSGR"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[XTPOPSGR], "XTPOPSGR"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[DECAC], "DECAC"),
|
||||
TraceLoggingUInt32(_uiTimesUsed[DECPS], "DECPS"),
|
||||
TraceLoggingUInt32Array(_uiTimesFailed, ARRAYSIZE(_uiTimesFailed), "Failed"),
|
||||
TraceLoggingUInt32(_uiTimesFailedOutsideRange, "FailedOutsideRange"));
|
||||
}
|
||||
|
||||
@@ -108,6 +108,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
XTPUSHSGR,
|
||||
XTPOPSGR,
|
||||
DECAC,
|
||||
DECPS,
|
||||
// Only use this last enum as a count of the number of codes.
|
||||
NUMBER_OF_CODES
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user