c910045187
*cfc72cee
(origin/dev/duhowett/ibxint, github/master) Make sure cursor blinks after opening new tab (1030) *9ad25440
Fix #936: misuse of uninitialized objects causes AppVerifier breaks on Windows Terminal startup (1015) *5f938a04
Update Terminal.cpp (1034) *4c47631b
Cleanup - termDispatch.hpp & adaptDispatch.hpp overrides (1004) *cc304759
add audit mode to ci (948) *80f10796
Fix the bell sound when Alt+key is pressed. (1006) *42e87ed3
fix build break from using `await` instead of `co_await` (1009) *40b557a4
Update manifest to correct 1903 version, unref param fix (1008) *0f62ec81
Eat all tap keypresses no matter what. (985) *ce0eaab9
inbox: Merge accumulated build fixes from RS_ONECORE_DEP_ACIOSS (1002) *1c509683
add .editorconfig file (585) *efd69990
Add support for OSC 10 and 11 to set the default colors (891) Related work items: #21610659, #21838182
248 lines
7.9 KiB
C++
248 lines
7.9 KiB
C++
// Copyright (c) Microsoft Corporation.
|
||
// Licensed under the MIT license.
|
||
|
||
|
||
#include "precomp.h"
|
||
|
||
#include "thread.hpp"
|
||
|
||
#pragma hdrstop
|
||
|
||
using namespace Microsoft::Console::Render;
|
||
|
||
RenderThread::RenderThread() :
|
||
_pRenderer(nullptr),
|
||
_hThread(nullptr),
|
||
_hEvent(nullptr),
|
||
_hPaintCompletedEvent(nullptr),
|
||
_fKeepRunning(true),
|
||
_hPaintEnabledEvent(nullptr)
|
||
{
|
||
|
||
}
|
||
|
||
RenderThread::~RenderThread()
|
||
{
|
||
if (_hThread)
|
||
{
|
||
_fKeepRunning = false; // stop loop after final run
|
||
SignalObjectAndWait(_hEvent, _hThread, INFINITE, FALSE); // signal final paint and wait for thread to finish.
|
||
|
||
CloseHandle(_hThread);
|
||
_hThread = nullptr;
|
||
}
|
||
|
||
if (_hEvent)
|
||
{
|
||
CloseHandle(_hEvent);
|
||
_hEvent = nullptr;
|
||
}
|
||
|
||
if (_hPaintEnabledEvent)
|
||
{
|
||
CloseHandle(_hPaintEnabledEvent);
|
||
_hPaintEnabledEvent = nullptr;
|
||
}
|
||
|
||
if (_hPaintCompletedEvent)
|
||
{
|
||
CloseHandle(_hPaintCompletedEvent);
|
||
_hPaintCompletedEvent = nullptr;
|
||
}
|
||
}
|
||
|
||
// Method Description:
|
||
// - Create all of the Events we'll need, and the actual thread we'll be doing
|
||
// work on.
|
||
// Arguments:
|
||
// - pRendererParent: the IRenderer that owns this thread, and which we should
|
||
// trigger frames for.
|
||
// Return Value:
|
||
// - S_OK if we succeeded, else an HRESULT corresponding to a failure to create
|
||
// an Event or Thread.
|
||
[[nodiscard]]
|
||
HRESULT RenderThread::Initialize(IRenderer* const pRendererParent) noexcept
|
||
{
|
||
_pRenderer = pRendererParent;
|
||
|
||
HRESULT hr = S_OK;
|
||
// Create event before thread as thread will start immediately.
|
||
if (SUCCEEDED(hr))
|
||
{
|
||
HANDLE hEvent = CreateEventW(nullptr, // non-inheritable security attributes
|
||
FALSE, // auto reset event
|
||
FALSE, // initially unsignaled
|
||
nullptr // no name
|
||
);
|
||
|
||
if (hEvent == nullptr)
|
||
{
|
||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
}
|
||
else
|
||
{
|
||
_hEvent = hEvent;
|
||
}
|
||
}
|
||
|
||
if (SUCCEEDED(hr))
|
||
{
|
||
HANDLE hPaintEnabledEvent = CreateEventW(nullptr,
|
||
TRUE, // manual reset event
|
||
FALSE, // initially signaled
|
||
nullptr);
|
||
|
||
if (hPaintEnabledEvent == nullptr)
|
||
{
|
||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
}
|
||
else
|
||
{
|
||
_hPaintEnabledEvent = hPaintEnabledEvent;
|
||
}
|
||
}
|
||
|
||
if (SUCCEEDED(hr))
|
||
{
|
||
HANDLE hPaintCompletedEvent = CreateEventW(nullptr,
|
||
TRUE, // manual reset event
|
||
TRUE, // initially signaled
|
||
nullptr);
|
||
|
||
if (hPaintCompletedEvent == nullptr)
|
||
{
|
||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
}
|
||
else
|
||
{
|
||
_hPaintCompletedEvent = hPaintCompletedEvent;
|
||
}
|
||
}
|
||
|
||
if (SUCCEEDED(hr))
|
||
{
|
||
HANDLE hThread = CreateThread(nullptr, // non-inheritable security attributes
|
||
0, // use default stack size
|
||
s_ThreadProc,
|
||
this,
|
||
0, // create immediately
|
||
nullptr // we don't need the thread ID
|
||
);
|
||
|
||
if (hThread == nullptr)
|
||
{
|
||
hr = HRESULT_FROM_WIN32(GetLastError());
|
||
}
|
||
else
|
||
{
|
||
_hThread = hThread;
|
||
}
|
||
}
|
||
|
||
return hr;
|
||
}
|
||
|
||
DWORD WINAPI RenderThread::s_ThreadProc(_In_ LPVOID lpParameter)
|
||
{
|
||
RenderThread* const pContext = static_cast<RenderThread*>(lpParameter);
|
||
|
||
if (pContext != nullptr)
|
||
{
|
||
return pContext->_ThreadProc();
|
||
}
|
||
else
|
||
{
|
||
return (DWORD)E_INVALIDARG;
|
||
}
|
||
}
|
||
|
||
DWORD WINAPI RenderThread::_ThreadProc()
|
||
{
|
||
while (_fKeepRunning)
|
||
{
|
||
WaitForSingleObject(_hPaintEnabledEvent, INFINITE);
|
||
WaitForSingleObject(_hEvent, INFINITE);
|
||
|
||
ResetEvent(_hPaintCompletedEvent);
|
||
|
||
LOG_IF_FAILED(_pRenderer->PaintFrame());
|
||
|
||
SetEvent(_hPaintCompletedEvent);
|
||
|
||
// extra check before we sleep since it's a "long" activity, relatively speaking.
|
||
if (_fKeepRunning)
|
||
{
|
||
Sleep(s_FrameLimitMilliseconds);
|
||
}
|
||
}
|
||
|
||
return S_OK;
|
||
}
|
||
|
||
void RenderThread::NotifyPaint()
|
||
{
|
||
SetEvent(_hEvent);
|
||
}
|
||
|
||
void RenderThread::EnablePainting()
|
||
{
|
||
SetEvent(_hPaintEnabledEvent);
|
||
}
|
||
|
||
void RenderThread::WaitForPaintCompletionAndDisable(const DWORD dwTimeoutMs)
|
||
{
|
||
// When rendering takes place via DirectX, and a console application
|
||
// currently owns the screen, and a new console application is launched (or
|
||
// the user switches to another console application), the new application
|
||
// cannot take over the screen until the active one relinquishes it. This
|
||
// blocking mechanism goes as follows:
|
||
//
|
||
// 1. The console input thread of the new console application connects to
|
||
// ConIoSrv;
|
||
// 2. While servicing the new connection request, ConIoSrv sends an event to
|
||
// the active application letting it know that it has lost focus;
|
||
// 3.1 ConIoSrv waits for a reply from the client application;
|
||
// 3.2 Meanwhile, the active application receives the focus event and calls
|
||
// the method this methed, waiting for the current paint operation to
|
||
// finish.
|
||
//
|
||
// This means that the new application is waiting on the connection request
|
||
// reply from ConIoSrv, ConIoSrv is waiting on the active application to
|
||
// acknowledge the lost focus event to reply to the new application, and the
|
||
// console input thread in the active application is waiting on the renderer
|
||
// thread to finish its current paint operation.
|
||
//
|
||
// Question: what should happen if the wait on the paint operation times
|
||
// out?
|
||
//
|
||
// There are three options:
|
||
//
|
||
// 1. On timeout, the active console application could reply with an error
|
||
// message and terminate itself, effectively relinquishing control of the
|
||
// display;
|
||
//
|
||
// 2. ConIoSrv itself could time out on waiting for a reply, and forcibly
|
||
// terminate the active console application;
|
||
//
|
||
// 3. Let the wait time out and let the user deal with it. Because the wait
|
||
// occurs on a single iteration of the renderer thread, it seemed to me that
|
||
// the likelihood of failure is extremely small, especially since the client
|
||
// console application that the active conhost instance is servicing has no
|
||
// say over what happens in the renderer thread, only by proxy. Thus, the
|
||
// chance of failure (timeout) is minimal and since the OneCoreUAP console
|
||
// is not a massively used piece of software, it didn’t seem that it would
|
||
// be a good use of time to build the requisite infrastructure to deal with
|
||
// a timeout here, at least not for now. In case of a timeout DirectX will
|
||
// catch the mistake of a new application attempting to acquire the display
|
||
// while another one still owns it and will flag it as a DWM bug. Right now,
|
||
// the active application will wait one second for the paint operation to
|
||
// finish.
|
||
//
|
||
// TODO: MSFT: 11833883 - Determine action when wait on paint operation via
|
||
// DirectX on OneCoreUAP times out while switching console
|
||
// applications.
|
||
|
||
ResetEvent(_hPaintEnabledEvent);
|
||
WaitForSingleObject(_hPaintCompletedEvent, dwTimeoutMs);
|
||
}
|