diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt
index fc07e9d2a22..6ddbe8c3f24 100644
--- a/.github/actions/spelling/expect/expect.txt
+++ b/.github/actions/spelling/expect/expect.txt
@@ -1942,6 +1942,7 @@ REGSTR
reingest
Relayout
RELBINPATH
+remoting
Remoting
renamer
renderengine
diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest
index fe8d0bb6fa7..f73f4c62f48 100644
--- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest
+++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest
@@ -98,7 +98,7 @@
-
+
diff --git a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest
index edb05aa7bc3..1c493b7edd7 100644
--- a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest
+++ b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest
@@ -99,7 +99,7 @@
-
+
diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest
index 34c943b3749..ad4472156e1 100644
--- a/src/cascadia/CascadiaPackage/Package.appxmanifest
+++ b/src/cascadia/CascadiaPackage/Package.appxmanifest
@@ -99,7 +99,7 @@
-
+
-->
diff --git a/src/host/PtySignalInputThread.cpp b/src/host/PtySignalInputThread.cpp
index c563adc6b6f..12197c8bc85 100644
--- a/src/host/PtySignalInputThread.cpp
+++ b/src/host/PtySignalInputThread.cpp
@@ -26,7 +26,7 @@ using namespace Microsoft::Console::VirtualTerminal;
// - Creates the PTY Signal Input Thread.
// Arguments:
// - hPipe - a handle to the file representing the read end of the VT pipe.
-PtySignalInputThread::PtySignalInputThread(_In_ wil::unique_hfile hPipe) :
+PtySignalInputThread::PtySignalInputThread(wil::unique_hfile hPipe) :
_hFile{ std::move(hPipe) },
_hThread{},
_pConApi{ std::make_unique(ServiceLocator::LocateGlobals().getConsoleInformation()) },
@@ -76,7 +76,7 @@ void PtySignalInputThread::ConnectConsole() noexcept
// - The ThreadProc for the PTY Signal Input Thread.
// Return Value:
// - S_OK if the thread runs to completion.
-// - Otherwise it may cause an application termination another route and never return.
+// - Otherwise it may cause an application termination and never return.
[[nodiscard]] HRESULT PtySignalInputThread::_InputThread()
{
unsigned short signalId;
diff --git a/src/host/exe/CConsoleHandoff.cpp b/src/host/exe/CConsoleHandoff.cpp
index 20ac3c6e806..eaf28088a22 100644
--- a/src/host/exe/CConsoleHandoff.cpp
+++ b/src/host/exe/CConsoleHandoff.cpp
@@ -27,11 +27,18 @@ static HRESULT _duplicateHandle(const HANDLE in, HANDLE& out)
// - server - Console driver server handle
// - inputEvent - Event already established that we signal when new input data is available in case the driver is waiting on us
// - msg - Portable attach message containing just enough descriptor payload to get us started in servicing it
+// - inboxProcess - Handle to the inbox process so we can watch it to see if it disappears on us.
+// - process - Handle to our process for waiting for us to exit
HRESULT CConsoleHandoff::EstablishHandoff(HANDLE server,
HANDLE inputEvent,
- PCCONSOLE_PORTABLE_ATTACH_MSG msg)
+ PCCONSOLE_PORTABLE_ATTACH_MSG msg,
+ HANDLE signalPipe,
+ HANDLE inboxProcess,
+ HANDLE* process)
try
{
+ RETURN_HR_IF(E_INVALIDARG, !process);
+
// Fill the descriptor portion of a fresh api message with the received data.
// The descriptor portion is the "received" packet from the last ask of the driver.
// The other portions are unnecessary as they track the other buffer state, error codes,
@@ -53,9 +60,20 @@ try
// Making our own duplicate copy ensures they hang around in our lifetime.
RETURN_IF_FAILED(_duplicateHandle(server, server));
RETURN_IF_FAILED(_duplicateHandle(inputEvent, inputEvent));
+ RETURN_IF_FAILED(_duplicateHandle(signalPipe, signalPipe));
+ RETURN_IF_FAILED(_duplicateHandle(inboxProcess, inboxProcess));
// Now perform the handoff.
- RETURN_IF_FAILED(ConsoleEstablishHandoff(server, inputEvent, &apiMsg));
+ RETURN_IF_FAILED(ConsoleEstablishHandoff(server, inputEvent, signalPipe, inboxProcess, &apiMsg));
+
+ // Give back a copy of our own process handle to be tracked.
+ RETURN_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(),
+ GetCurrentProcess(),
+ GetCurrentProcess(),
+ process,
+ SYNCHRONIZE,
+ FALSE,
+ 0));
return S_OK;
}
diff --git a/src/host/exe/CConsoleHandoff.h b/src/host/exe/CConsoleHandoff.h
index e2d2e14d890..36ad564f620 100644
--- a/src/host/exe/CConsoleHandoff.h
+++ b/src/host/exe/CConsoleHandoff.h
@@ -34,7 +34,10 @@ struct __declspec(uuid(__CLSID_CConsoleHandoff))
#pragma region IConsoleHandoff
STDMETHODIMP EstablishHandoff(HANDLE server,
HANDLE inputEvent,
- PCCONSOLE_PORTABLE_ATTACH_MSG msg);
+ PCCONSOLE_PORTABLE_ATTACH_MSG msg,
+ HANDLE signalPipe,
+ HANDLE inboxProcess,
+ HANDLE* process);
#pragma endregion
};
diff --git a/src/host/globals.h b/src/host/globals.h
index c99794845fa..a955e991f87 100644
--- a/src/host/globals.h
+++ b/src/host/globals.h
@@ -74,6 +74,8 @@ class Globals
std::optional handoffConsoleClsid;
std::optional handoffTerminalClsid;
+ wil::unique_hfile handoffInboxConsoleHandle;
+ wil::unique_threadpool_wait handoffInboxConsoleExitWait;
#ifdef UNIT_TESTING
void EnableConptyModeForTests(std::unique_ptr vtRenderEngine);
diff --git a/src/host/proxy/IConsoleHandoff.idl b/src/host/proxy/IConsoleHandoff.idl
index 067eb0efa83..d7e8d63bc53 100644
--- a/src/host/proxy/IConsoleHandoff.idl
+++ b/src/host/proxy/IConsoleHandoff.idl
@@ -20,11 +20,14 @@ typedef const CONSOLE_PORTABLE_ATTACH_MSG* PCCONSOLE_PORTABLE_ATTACH_MSG;
[
object,
- uuid(2B607BC1-43EB-40C3-95AE-2856ADDB7F23)
+ uuid(E686C757-9A35-4A1C-B3CE-0BCC8B5C69F4)
] interface IConsoleHandoff : IUnknown
{
HRESULT EstablishHandoff([in, system_handle(sh_file)] HANDLE server,
[in, system_handle(sh_event)] HANDLE inputEvent,
- [in, ref] PCCONSOLE_PORTABLE_ATTACH_MSG msg);
+ [in, ref] PCCONSOLE_PORTABLE_ATTACH_MSG msg,
+ [in, system_handle(sh_pipe)] HANDLE signalPipe,
+ [in, system_handle(sh_process)] HANDLE inboxProcess,
+ [out, system_handle(sh_process)] HANDLE* process);
};
diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp
index bebec95b2fd..c1ed6020d33 100644
--- a/src/host/srvinit.cpp
+++ b/src/host/srvinit.cpp
@@ -20,6 +20,7 @@
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../interactivity/base/ApiDetector.hpp"
+#include "../interactivity/base/RemoteConsoleControl.hpp"
#include "renderData.hpp"
#include "../renderer/base/renderer.hpp"
@@ -364,6 +365,8 @@ HRESULT ConsoleCreateIoThread(_In_ HANDLE Server,
// from the driver... or an S_OK success.
[[nodiscard]] HRESULT ConsoleEstablishHandoff([[maybe_unused]] _In_ HANDLE Server,
[[maybe_unused]] HANDLE driverInputEvent,
+ [[maybe_unused]] HANDLE hostSignalPipe,
+ [[maybe_unused]] HANDLE hostProcessHandle,
[[maybe_unused]] PCONSOLE_API_MSG connectMessage)
try
{
@@ -388,6 +391,23 @@ try
return E_NOT_SET;
}
+ // Capture handle to the inbox process into a unique handle holder.
+ g.handoffInboxConsoleHandle.reset(hostProcessHandle);
+
+ // Set up a threadpool waiter to shutdown everything if the inbox process disappears.
+ g.handoffInboxConsoleExitWait.reset(CreateThreadpoolWait(
+ [](PTP_CALLBACK_INSTANCE /*callbackInstance*/, PVOID /*context*/, PTP_WAIT /*wait*/, TP_WAIT_RESULT /*waitResult*/) noexcept {
+ ServiceLocator::RundownAndExit(E_APPLICATION_MANAGER_NOT_RUNNING);
+ },
+ nullptr,
+ nullptr));
+ RETURN_LAST_ERROR_IF_NULL(g.handoffInboxConsoleExitWait.get());
+
+ SetThreadpoolWait(g.handoffInboxConsoleExitWait.get(), g.handoffInboxConsoleHandle.get(), nullptr);
+
+ std::unique_ptr remoteControl = std::make_unique(hostSignalPipe);
+ RETURN_IF_NTSTATUS_FAILED(ServiceLocator::SetConsoleControlInstance(std::move(remoteControl)));
+
wil::unique_handle signalPipeTheirSide;
wil::unique_handle signalPipeOurSide;
@@ -397,20 +417,11 @@ try
wil::unique_handle outPipeTheirSide;
wil::unique_handle outPipeOurSide;
- SECURITY_ATTRIBUTES sa;
- sa.nLength = sizeof(sa);
- // Mark inheritable for signal handle when creating. It'll have the same value on the other side.
- sa.bInheritHandle = TRUE;
- sa.lpSecurityDescriptor = nullptr;
-
RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(signalPipeOurSide.addressof(), signalPipeTheirSide.addressof(), nullptr, 0));
- RETURN_IF_WIN32_BOOL_FALSE(SetHandleInformation(signalPipeTheirSide.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(inPipeOurSide.addressof(), inPipeTheirSide.addressof(), nullptr, 0));
- RETURN_IF_WIN32_BOOL_FALSE(SetHandleInformation(inPipeTheirSide.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
RETURN_IF_WIN32_BOOL_FALSE(CreatePipe(outPipeTheirSide.addressof(), outPipeOurSide.addressof(), nullptr, 0));
- RETURN_IF_WIN32_BOOL_FALSE(SetHandleInformation(outPipeTheirSide.get(), HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT));
wil::unique_handle clientProcess{ OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, TRUE, static_cast(connectMessage->Descriptor.Process)) };
RETURN_LAST_ERROR_IF_NULL(clientProcess.get());
@@ -434,9 +445,9 @@ try
serverProcess,
clientProcess.get()));
- inPipeTheirSide.release();
- outPipeTheirSide.release();
- signalPipeTheirSide.release();
+ inPipeTheirSide.reset();
+ outPipeTheirSide.reset();
+ signalPipeTheirSide.reset();
const auto commandLine = fmt::format(FMT_COMPILE(L" --headless --signal {:#x}"), (int64_t)signalPipeOurSide.release());
diff --git a/src/host/srvinit.h b/src/host/srvinit.h
index 3b2725a390e..6fac6771897 100644
--- a/src/host/srvinit.h
+++ b/src/host/srvinit.h
@@ -30,6 +30,8 @@ PWSTR TranslateConsoleTitle(_In_ PCWSTR pwszConsoleTitle, const BOOL fUnexpand,
[[nodiscard]] HRESULT ConsoleEstablishHandoff(_In_ HANDLE Server,
HANDLE driverInputEvent,
+ HANDLE hostSignalPipe,
+ HANDLE hostProcessHandle,
PCONSOLE_API_MSG connectMessage);
void ConsoleCheckDebug();
diff --git a/src/inc/HostSignals.hpp b/src/inc/HostSignals.hpp
new file mode 100644
index 00000000000..05e1d0f3f72
--- /dev/null
+++ b/src/inc/HostSignals.hpp
@@ -0,0 +1,33 @@
+namespace Microsoft::Console
+{
+ // These values match the enumeration values of `ControlType` for the `ConsoleControl` class
+ // but are defined here similarly to not pollute other projects.
+ // They don't *have* to be the same values, but matching them seemed to make sense.
+ enum class HostSignals : uint8_t
+ {
+ NotifyApp = 1u,
+ SetForeground = 5u,
+ EndTask = 7u
+ };
+
+ struct HostSignalNotifyAppData
+ {
+ uint32_t sizeInBytes;
+ uint32_t processId;
+ };
+
+ struct HostSignalSetForegroundData
+ {
+ uint32_t sizeInBytes;
+ uint32_t processId;
+ bool isForeground;
+ };
+
+ struct HostSignalEndTaskData
+ {
+ uint32_t sizeInBytes;
+ uint32_t processId;
+ uint32_t eventType;
+ uint32_t ctrlFlags;
+ };
+};
\ No newline at end of file
diff --git a/src/inc/LibraryIncludes.h b/src/inc/LibraryIncludes.h
index 996f68d247d..b19b42f16a6 100644
--- a/src/inc/LibraryIncludes.h
+++ b/src/inc/LibraryIncludes.h
@@ -53,6 +53,7 @@
// WIL
#include
#include
+#include
#include
#include
#include
diff --git a/src/interactivity/base/HostSignalInputThread.cpp b/src/interactivity/base/HostSignalInputThread.cpp
new file mode 100644
index 00000000000..2dc71ccca20
--- /dev/null
+++ b/src/interactivity/base/HostSignalInputThread.cpp
@@ -0,0 +1,216 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "precomp.h"
+
+#include "HostSignalInputThread.hpp"
+#include "../inc/HostSignals.hpp"
+
+#include "../interactivity/inc/ServiceLocator.hpp"
+
+using namespace Microsoft::Console;
+using namespace Microsoft::Console::Interactivity;
+
+// Constructor Description:
+// - Creates the PTY Signal Input Thread.
+// Arguments:
+// - hPipe - a handle to the file representing the read end of the Host Signal pipe.
+HostSignalInputThread::HostSignalInputThread(wil::unique_hfile&& hPipe) :
+ _hFile{ std::move(hPipe) },
+ _hThread{},
+ _dwThreadId{}
+{
+ THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE);
+ THROW_HR_IF(E_HANDLE, _hFile.get() == nullptr);
+}
+
+HostSignalInputThread::~HostSignalInputThread()
+{
+ // Manually terminate our thread during unittesting. Otherwise, the test
+ // will finish, but TAEF will not manually kill the test.
+#ifdef UNIT_TESTING
+ TerminateThread(_hThread.get(), 0);
+#endif
+}
+
+// Function Description:
+// - Static function used for initializing an instance's ThreadProc.
+// Arguments:
+// - lpParameter - A pointer to the HostSignalInputThread instance that should be called.
+// Return Value:
+// - The return value of the underlying instance's _InputThread
+DWORD WINAPI HostSignalInputThread::StaticThreadProc(LPVOID lpParameter)
+{
+ HostSignalInputThread* const pInstance = static_cast(lpParameter);
+ return pInstance->_InputThread();
+}
+
+// Method Description:
+// - Attempts to retrieve a given type T off of the internal
+// pipe channel and return it.
+// Return Value:
+// - A structure filled with the specified data off the byte stream
+// - EXCEPTIONS may be thrown if the packet size mismatches
+// or if we fail to read for another reason.
+template
+T HostSignalInputThread::_ReceiveTypedPacket()
+{
+ T msg = { 0 };
+ THROW_HR_IF(E_ABORT, !_GetData(gsl::as_writable_bytes(gsl::span{ &msg, 1 })));
+
+ // If the message is smaller than what we expected
+ // then it was malformed and we need to throw.
+ THROW_HR_IF(E_ILLEGAL_METHOD_CALL, msg.sizeInBytes < sizeof(msg));
+
+ // If the message size was stated to be larger, we
+ // want to seek forward to the next message code.
+ // If it's equal, we'll seek forward by 0 and
+ // do nothing.
+ _AdvanceReader(msg.sizeInBytes - sizeof(msg));
+
+ return msg;
+}
+
+// Method Description:
+// - The ThreadProc for the Host Signal Input Thread.
+// Return Value:
+// - S_OK if the thread runs to completion.
+// - Otherwise it may cause an application termination and never return.
+[[nodiscard]] HRESULT HostSignalInputThread::_InputThread()
+{
+ HostSignals signalId;
+
+ while (_GetData(gsl::as_writable_bytes(gsl::span{ &signalId, 1 })))
+ {
+ switch (signalId)
+ {
+ case HostSignals::NotifyApp:
+ {
+ auto msg = _ReceiveTypedPacket();
+
+ LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->NotifyConsoleApplication(msg.processId));
+
+ break;
+ }
+ case HostSignals::SetForeground:
+ {
+ auto msg = _ReceiveTypedPacket();
+
+ LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->SetForeground(ULongToHandle(msg.processId), msg.isForeground));
+
+ break;
+ }
+ case HostSignals::EndTask:
+ {
+ auto msg = _ReceiveTypedPacket();
+
+ LOG_IF_NTSTATUS_FAILED(ServiceLocator::LocateConsoleControl()->EndTask(ULongToHandle(msg.processId), msg.eventType, msg.ctrlFlags));
+
+ break;
+ }
+ default:
+ {
+ THROW_HR(E_UNEXPECTED);
+ break;
+ }
+ }
+ }
+ return S_OK;
+}
+
+// Method Description:
+// - Skips the file stream forward by the specified number of bytes.
+// Arguments:
+// - byteCount - Count of bytes to skip forward
+// Return Value:
+// - True if we could skip forward successfully. False otherwise.
+bool HostSignalInputThread::_AdvanceReader(DWORD byteCount)
+{
+ std::array buffer;
+
+ while (byteCount > 0)
+ {
+ const auto advance = std::min(byteCount, gsl::narrow_cast(buffer.max_size()));
+
+ if (!_GetData(buffer))
+ {
+ return false;
+ }
+
+ byteCount -= advance;
+ }
+
+ return true;
+}
+
+// Method Description:
+// - Retrieves bytes from the file stream and exits or throws errors should the pipe state
+// be compromised.
+// Arguments:
+// - buffer - Buffer to fill with data.
+// Return Value:
+// - True if data was retrieved successfully. False otherwise.
+bool HostSignalInputThread::_GetData(gsl::span buffer)
+{
+ DWORD bytesRead = 0;
+ // If we failed to read because the terminal broke our pipe (usually due
+ // to dying itself), close gracefully with ERROR_BROKEN_PIPE.
+ // Otherwise throw an exception. ERROR_BROKEN_PIPE is the only case that
+ // we want to gracefully close in.
+ if (FALSE == ReadFile(_hFile.get(), buffer.data(), gsl::narrow_cast(buffer.size()), &bytesRead, nullptr))
+ {
+ DWORD lastError = GetLastError();
+ if (lastError == ERROR_BROKEN_PIPE)
+ {
+ _Shutdown();
+ return false;
+ }
+
+ THROW_WIN32(lastError);
+ }
+
+ if (bytesRead != buffer.size())
+ {
+ _Shutdown();
+ return false;
+ }
+
+ return true;
+}
+
+// Method Description:
+// - Starts the PTY Signal input thread.
+[[nodiscard]] HRESULT HostSignalInputThread::Start() noexcept
+{
+ // 0 is the right value, https://devblogs.microsoft.com/oldnewthing/20040223-00/?p=40503
+ _dwThreadId = 0;
+
+ _hThread.reset(CreateThread(nullptr,
+ 0,
+ HostSignalInputThread::StaticThreadProc,
+ this,
+ 0,
+ &_dwThreadId));
+
+ RETURN_LAST_ERROR_IF_NULL(_hThread.get());
+ LOG_IF_FAILED(SetThreadDescription(_hThread.get(), L"Host Signal Handler Thread"));
+
+ return S_OK;
+}
+
+// Method Description:
+// - Perform a shutdown of the console. This happens when the signal pipe is
+// broken, which means either the parent terminal process has died, or they
+// called ClosePseudoConsole.
+// CloseConsoleProcessState is not enough by itself - it will disconnect
+// clients as if the X was pressed, but then we need to actually still die,
+// so then call RundownAndExit.
+// Arguments:
+// -
+// Return Value:
+// -
+void HostSignalInputThread::_Shutdown()
+{
+ // Make sure we terminate.
+ ServiceLocator::RundownAndExit(ERROR_BROKEN_PIPE);
+}
diff --git a/src/interactivity/base/HostSignalInputThread.hpp b/src/interactivity/base/HostSignalInputThread.hpp
new file mode 100644
index 00000000000..429e492955b
--- /dev/null
+++ b/src/interactivity/base/HostSignalInputThread.hpp
@@ -0,0 +1,48 @@
+/*++
+Copyright (c) Microsoft Corporation
+Licensed under the MIT license.
+
+Module Name:
+- HostSignalInputThread.hpp
+
+Abstract:
+- Defines methods that wrap the thread that will wait for signals
+ from a delegated console host to this "owner" console.
+
+Author(s):
+- Michael Niksa (miniksa) 10 Jun 2021
+
+Notes:
+- Sourced from `PtySignalInputThread`
+--*/
+#pragma once
+
+namespace Microsoft::Console
+{
+ class HostSignalInputThread final
+ {
+ public:
+ HostSignalInputThread(wil::unique_hfile&& hPipe);
+ ~HostSignalInputThread();
+
+ [[nodiscard]] HRESULT Start() noexcept;
+ static DWORD WINAPI StaticThreadProc(LPVOID lpParameter);
+
+ // Prevent copying and assignment.
+ HostSignalInputThread(const HostSignalInputThread&) = delete;
+ HostSignalInputThread& operator=(const HostSignalInputThread&) = delete;
+
+ private:
+ template
+ T _ReceiveTypedPacket();
+ [[nodiscard]] HRESULT _InputThread();
+
+ bool _GetData(gsl::span buffer);
+ bool _AdvanceReader(DWORD byteCount);
+ void _Shutdown();
+
+ DWORD _dwThreadId;
+ wil::unique_hfile _hFile;
+ wil::unique_handle _hThread;
+ };
+}
diff --git a/src/interactivity/base/RemoteConsoleControl.cpp b/src/interactivity/base/RemoteConsoleControl.cpp
new file mode 100644
index 00000000000..21b8155a79f
--- /dev/null
+++ b/src/interactivity/base/RemoteConsoleControl.cpp
@@ -0,0 +1,79 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+#include "precomp.h"
+
+#include "RemoteConsoleControl.hpp"
+
+#include "../../inc/HostSignals.hpp"
+
+using namespace Microsoft::Console::Interactivity;
+
+RemoteConsoleControl::RemoteConsoleControl(HANDLE signalPipe) :
+ _pipe{ signalPipe }
+{
+}
+
+#pragma region IConsoleControl Members
+
+template
+[[nodiscard]] NTSTATUS _SendTypedPacket(HANDLE pipe, ::Microsoft::Console::HostSignals signalCode, T& payload)
+{
+ // To ensure it's a happy wire format, pack it tight at 1.
+#pragma pack(push, 1)
+ struct HostSignalPacket
+ {
+ ::Microsoft::Console::HostSignals code;
+ T data;
+ };
+#pragma pack(pop)
+
+ HostSignalPacket packet;
+ packet.code = signalCode;
+ packet.data = payload;
+
+ DWORD bytesWritten = 0;
+ if (!WriteFile(pipe, &packet, sizeof(packet), &bytesWritten, nullptr))
+ {
+ NT_RETURN_NTSTATUS(static_cast(NTSTATUS_FROM_WIN32(::GetLastError())));
+ }
+
+ if (bytesWritten != sizeof(packet))
+ {
+ NT_RETURN_NTSTATUS(static_cast(NTSTATUS_FROM_WIN32(E_UNEXPECTED)));
+ }
+
+ return STATUS_SUCCESS;
+}
+
+[[nodiscard]] NTSTATUS RemoteConsoleControl::NotifyConsoleApplication(_In_ DWORD dwProcessId)
+{
+ HostSignalNotifyAppData data{};
+ data.sizeInBytes = sizeof(data);
+ data.processId = dwProcessId;
+
+ return _SendTypedPacket(_pipe.get(), HostSignals::NotifyApp, data);
+}
+
+[[nodiscard]] NTSTATUS RemoteConsoleControl::SetForeground(_In_ HANDLE hProcess, _In_ BOOL fForeground)
+{
+ HostSignalSetForegroundData data{};
+ data.sizeInBytes = sizeof(data);
+ data.processId = HandleToULong(hProcess);
+ data.isForeground = fForeground;
+
+ return _SendTypedPacket(_pipe.get(), HostSignals::SetForeground, data);
+}
+
+[[nodiscard]] NTSTATUS RemoteConsoleControl::EndTask(_In_ HANDLE hProcessId, _In_ DWORD dwEventType, _In_ ULONG ulCtrlFlags)
+{
+ HostSignalEndTaskData data{};
+ data.sizeInBytes = sizeof(data);
+ data.processId = HandleToULong(hProcessId);
+ data.eventType = dwEventType;
+ data.ctrlFlags = ulCtrlFlags;
+
+ return _SendTypedPacket(_pipe.get(), HostSignals::EndTask, data);
+}
+
+#pragma endregion
diff --git a/src/interactivity/base/RemoteConsoleControl.hpp b/src/interactivity/base/RemoteConsoleControl.hpp
new file mode 100644
index 00000000000..52de8db1eb3
--- /dev/null
+++ b/src/interactivity/base/RemoteConsoleControl.hpp
@@ -0,0 +1,33 @@
+/*++
+Copyright (c) Microsoft Corporation
+Licensed under the MIT license.
+
+Module Name:
+- RemoteConsoleControl.hpp
+
+Abstract:
+- This module is used for remoting console control calls to a different host owner process.
+
+Author(s):
+- Michael Niksa (MiNiksa) 10-Jun-2021
+--*/
+#pragma once
+
+#include "../inc/IConsoleControl.hpp"
+
+namespace Microsoft::Console::Interactivity
+{
+ class RemoteConsoleControl final : public IConsoleControl
+ {
+ public:
+ RemoteConsoleControl(HANDLE signalPipe);
+
+ // IConsoleControl Members
+ [[nodiscard]] NTSTATUS NotifyConsoleApplication(_In_ DWORD dwProcessId);
+ [[nodiscard]] NTSTATUS SetForeground(_In_ HANDLE hProcess, _In_ BOOL fForeground);
+ [[nodiscard]] NTSTATUS EndTask(_In_ HANDLE hProcessId, _In_ DWORD dwEventType, _In_ ULONG ulCtrlFlags);
+
+ private:
+ wil::unique_handle _pipe;
+ };
+}
diff --git a/src/interactivity/base/ServiceLocator.cpp b/src/interactivity/base/ServiceLocator.cpp
index 14d088368af..40fad8e9cbf 100644
--- a/src/interactivity/base/ServiceLocator.cpp
+++ b/src/interactivity/base/ServiceLocator.cpp
@@ -104,6 +104,24 @@ void ServiceLocator::RundownAndExit(const HRESULT hr)
#pragma region Set Methods
+[[nodiscard]] NTSTATUS ServiceLocator::SetConsoleControlInstance(_In_ std::unique_ptr&& control)
+{
+ if (s_consoleControl)
+ {
+ NT_RETURN_NTSTATUS(STATUS_INVALID_HANDLE);
+ }
+ else if (!control)
+ {
+ NT_RETURN_NTSTATUS(STATUS_INVALID_PARAMETER);
+ }
+ else
+ {
+ s_consoleControl = std::move(control);
+ }
+
+ return STATUS_SUCCESS;
+}
+
[[nodiscard]] NTSTATUS ServiceLocator::SetConsoleWindowInstance(_In_ IConsoleWindow* window)
{
NTSTATUS status = STATUS_SUCCESS;
diff --git a/src/interactivity/base/lib/InteractivityBase.vcxproj b/src/interactivity/base/lib/InteractivityBase.vcxproj
index bf64e6cf45d..0986a133f5a 100644
--- a/src/interactivity/base/lib/InteractivityBase.vcxproj
+++ b/src/interactivity/base/lib/InteractivityBase.vcxproj
@@ -6,7 +6,7 @@
Base
InteractivityBase
ConInteractivityBaseLib
- StaticLibrary
+ StaticLibrary
@@ -21,11 +21,13 @@
-
+
+
Create
+
@@ -40,12 +42,14 @@
-
+
+
+
-
+
\ No newline at end of file
diff --git a/src/interactivity/base/lib/InteractivityBase.vcxproj.filters b/src/interactivity/base/lib/InteractivityBase.vcxproj.filters
index 58825ba6f04..b7f9e8e1890 100644
--- a/src/interactivity/base/lib/InteractivityBase.vcxproj.filters
+++ b/src/interactivity/base/lib/InteractivityBase.vcxproj.filters
@@ -27,9 +27,15 @@
Source Files
-
- Source Files
-
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
@@ -41,9 +47,6 @@
Header Files
-
- Source Files
-
Header Files
@@ -77,9 +80,18 @@
Header Files
-
- Header Files
-
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
+
+ Header Files
+
diff --git a/src/interactivity/base/sources.inc b/src/interactivity/base/sources.inc
index 4881f791184..9521ea7c0ed 100644
--- a/src/interactivity/base/sources.inc
+++ b/src/interactivity/base/sources.inc
@@ -43,7 +43,9 @@ SOURCES = \
..\InteractivityFactory.cpp \
..\ServiceLocator.cpp \
..\VtApiRedirection.cpp \
- ..\EventSynthesis.cpp \
+ ..\EventSynthesis.cpp \
+ ..\RemoteConsoleControl.cpp \
+ ..\HostSignalInputThread.cpp \
INCLUDES = \
$(INCLUDES); \
diff --git a/src/interactivity/inc/ServiceLocator.hpp b/src/interactivity/inc/ServiceLocator.hpp
index 888855b4a3a..a7b0dc89a86 100644
--- a/src/interactivity/inc/ServiceLocator.hpp
+++ b/src/interactivity/inc/ServiceLocator.hpp
@@ -39,6 +39,7 @@ namespace Microsoft::Console::Interactivity
static IAccessibilityNotifier* LocateAccessibilityNotifier();
+ [[nodiscard]] static NTSTATUS SetConsoleControlInstance(_In_ std::unique_ptr&& control);
static IConsoleControl* LocateConsoleControl();
template
static T* LocateConsoleControl()
diff --git a/src/interactivity/win32/ConsoleControl.hpp b/src/interactivity/win32/ConsoleControl.hpp
index aa8b10916b9..9a8ab927fc0 100644
--- a/src/interactivity/win32/ConsoleControl.hpp
+++ b/src/interactivity/win32/ConsoleControl.hpp
@@ -3,7 +3,7 @@ Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
-- userdpiapi.hpp
+- ConsoleControl.hpp
Abstract:
- This module is used for abstracting calls to private user32 DLL APIs to break the build system dependency.
diff --git a/src/server/IoDispatchers.cpp b/src/server/IoDispatchers.cpp
index a55419c17e1..1d76c6d4c30 100644
--- a/src/server/IoDispatchers.cpp
+++ b/src/server/IoDispatchers.cpp
@@ -14,6 +14,7 @@
#include "../host/srvinit.h"
#include "../host/telemetry.hpp"
+#include "../interactivity/base/HostSignalInputThread.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
#include "../types/inc/utils.hpp"
@@ -296,16 +297,50 @@ PCONSOLE_API_MSG IoDispatchers::ConsoleHandleConnectionRequest(_In_ PCONSOLE_API
HANDLE serverHandle;
THROW_IF_FAILED(Globals.pDeviceComm->GetServerHandle(&serverHandle));
+ wil::unique_hfile signalPipeTheirSide;
+ wil::unique_hfile signalPipeOurSide;
+
+ THROW_IF_WIN32_BOOL_FALSE(CreatePipe(signalPipeOurSide.addressof(), signalPipeTheirSide.addressof(), nullptr, 0));
+
+ // Give a copy of our own process handle to be tracked.
+ wil::unique_process_handle ourProcess;
+ THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(),
+ GetCurrentProcess(),
+ GetCurrentProcess(),
+ ourProcess.addressof(),
+ SYNCHRONIZE,
+ FALSE,
+ 0));
+
+ wil::unique_process_handle clientProcess;
+
// Okay, moment of truth! If they say they successfully took it over, we're going to clean up.
// If they fail, we'll throw here and it'll log and we'll just start normally.
THROW_IF_FAILED(handoff->EstablishHandoff(serverHandle,
Globals.hInputEvent.get(),
- &msg));
+ &msg,
+ signalPipeTheirSide.get(),
+ ourProcess.get(),
+ &clientProcess));
+
+ // Close handles for the things we gave to them
+ signalPipeTheirSide.reset();
+ ourProcess.reset();
+ Globals.hInputEvent.reset();
+
+ // Start a thread to listen for signals from their side that we must relay to the OS.
+ auto hostSignalThread = std::make_unique(std::move(signalPipeOurSide));
+
+ // Start it if it was successfully created.
+ THROW_IF_FAILED(hostSignalThread->Start());
// Unlock in case anything tries to spool down as we exit.
UnlockConsole();
- // We've handed off responsibility. Exit process to clean up any outstanding things we have open.
+ // We've handed off responsibility. Wait for child process to exit so we can maintain PID continuity for some clients.
+ WaitForSingleObject(clientProcess.get(), INFINITE);
+
+ // Exit process to clean up any outstanding things we have open.
ExitProcess(S_OK);
}
CATCH_LOG(); // Just log, don't do anything more. We'll move on to launching normally on failure.