diff --git a/.github/actions/spelling/expect/alphabet.txt b/.github/actions/spelling/expect/alphabet.txt index bb8a43b36..6156f69d1 100644 --- a/.github/actions/spelling/expect/alphabet.txt +++ b/.github/actions/spelling/expect/alphabet.txt @@ -27,6 +27,7 @@ BBBBBBBBBBBBBBDDDD BBBBBCCC BBBBCCCCC BBGGRR +CCE EFG EFGh QQQQQQQQQQABCDEFGHIJ diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index fb976b366..c092347e4 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -1850,7 +1850,6 @@ psp PSPCB psr PSTR -psuedoconsole psz ptch ptr diff --git a/samples/ConPTY/GUIConsole/GUIConsole.ConPTY/Terminal.cs b/samples/ConPTY/GUIConsole/GUIConsole.ConPTY/Terminal.cs index bedc7b117..dc01dc66b 100644 --- a/samples/ConPTY/GUIConsole/GUIConsole.ConPTY/Terminal.cs +++ b/samples/ConPTY/GUIConsole/GUIConsole.ConPTY/Terminal.cs @@ -33,7 +33,7 @@ namespace GUIConsole.ConPTY } /// - /// Start the psuedoconsole and run the process as shown in + /// Start the pseudoconsole and run the process as shown in /// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#creating-the-pseudoconsole /// /// the command to run, e.g. cmd.exe diff --git a/samples/ConPTY/MiniTerm/MiniTerm/Terminal.cs b/samples/ConPTY/MiniTerm/MiniTerm/Terminal.cs index df4bb2173..10df9364e 100644 --- a/samples/ConPTY/MiniTerm/MiniTerm/Terminal.cs +++ b/samples/ConPTY/MiniTerm/MiniTerm/Terminal.cs @@ -41,7 +41,7 @@ namespace MiniTerm } /// - /// Start the psuedoconsole and run the process as shown in + /// Start the pseudoconsole and run the process as shown in /// https://docs.microsoft.com/en-us/windows/console/creating-a-pseudoconsole-session#creating-the-pseudoconsole /// /// the command to run, e.g. cmd.exe diff --git a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest index 10fe7e75c..fe8d0bb6f 100644 --- a/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Dev.appxmanifest @@ -99,7 +99,7 @@ - + diff --git a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest index 5c03e61cb..edb05aa7b 100644 --- a/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package-Pre.appxmanifest @@ -100,7 +100,7 @@ - + diff --git a/src/cascadia/CascadiaPackage/Package.appxmanifest b/src/cascadia/CascadiaPackage/Package.appxmanifest index 02ed1df0d..34c943b37 100644 --- a/src/cascadia/CascadiaPackage/Package.appxmanifest +++ b/src/cascadia/CascadiaPackage/Package.appxmanifest @@ -100,7 +100,7 @@ - + --> diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index 4c9e7612b..cd46e446d 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -1206,13 +1206,25 @@ namespace winrt::TerminalApp::implementation // in and be routed to an event with no handlers or a non-ready Page. if (_appArgs.IsHandoffListener()) { - _root->SetInboundListener(); + SetInboundListener(); } } return result; } + // Method Description: + // - Triggers the setup of the listener for incoming console connections + // from the operating system. + // Arguments: + // - + // Return Value: + // - + void AppLogic::SetInboundListener() + { + _root->SetInboundListener(); + } + // Method Description: // - Parse the provided commandline arguments into actions, and try to // perform them immediately. diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index c86b54300..da19d1e31 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -79,6 +79,8 @@ namespace winrt::TerminalApp::implementation Windows::UI::Xaml::UIElement GetRoot() noexcept; + void SetInboundListener(); + hstring Title(); void TitlebarClicked(); bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down); diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index fec67c970..92bd09613 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -42,6 +42,8 @@ namespace TerminalApp void LoadSettings(); Windows.UI.Xaml.UIElement GetRoot(); + void SetInboundListener(); + String Title { get; }; Boolean FocusMode { get; }; diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 91dd1ed70..41be3b846 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -325,23 +325,38 @@ namespace winrt::TerminalApp::implementation // This MUST be done after we've registered the event listener for the new connections // or the COM server might start receiving requests on another thread and dispatch // them to nowhere. - if (_shouldStartInboundListener) + _StartInboundListener(); + } + } + + // Routine Description: + // - Will start the listener for inbound console handoffs if we have already determined + // that we should do so. + // NOTE: Must be after TerminalPage::_OnNewConnection has been connected up. + // Arguments: + // - - Looks at _shouldStartInboundListener + // Return Value: + // - - May fail fast if setup fails as that would leave us in a weird state. + void TerminalPage::_StartInboundListener() + { + if (_shouldStartInboundListener) + { + _shouldStartInboundListener = false; + + try { - try - { - winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::StartInboundListener(); - } - // If we failed to start the listener, it will throw. - // We should fail fast here or the Terminal will be in a very strange state. - // We only start the listener if the Terminal was started with the COM server - // `-Embedding` flag and we make no tabs as a result. - // Therefore, if the listener cannot start itself up to make that tab with - // the inbound connection that caused the COM activation in the first place... - // we would be left with an empty terminal frame with no tabs. - // Instead, crash out so COM sees the server die and things unwind - // without a weird empty frame window. - CATCH_FAIL_FAST() + winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::StartInboundListener(); } + // If we failed to start the listener, it will throw. + // We should fail fast here or the Terminal will be in a very strange state. + // We only start the listener if the Terminal was started with the COM server + // `-Embedding` flag and we make no tabs as a result. + // Therefore, if the listener cannot start itself up to make that tab with + // the inbound connection that caused the COM activation in the first place... + // we would be left with an empty terminal frame with no tabs. + // Instead, crash out so COM sees the server die and things unwind + // without a weird empty frame window. + CATCH_FAIL_FAST() } } @@ -1972,6 +1987,13 @@ namespace winrt::TerminalApp::implementation void TerminalPage::SetInboundListener() { _shouldStartInboundListener = true; + + // If the page has already passed the NotInitialized state, + // then it is ready-enough for us to just start this immediately. + if (_startupState != StartupState::NotInitialized) + { + _StartInboundListener(); + } } winrt::TerminalApp::IDialogPresenter TerminalPage::DialogPresenter() const diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 37a1af8ab..a034b2b5f 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -301,6 +301,8 @@ namespace winrt::TerminalApp::implementation void _SetNewTabButtonColor(const Windows::UI::Color& color, const Windows::UI::Color& accentColor); void _ClearNewTabButtonColor(); + void _StartInboundListener(); + void _CompleteInitialization(); void _FocusActiveControl(IInspectable sender, IInspectable eventArgs); diff --git a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp index 276111903..0138921fd 100644 --- a/src/cascadia/TerminalConnection/CTerminalHandoff.cpp +++ b/src/cascadia/TerminalConnection/CTerminalHandoff.cpp @@ -84,12 +84,14 @@ static HRESULT _duplicateHandle(const HANDLE in, HANDLE& out) noexcept // - in - PTY input handle that we will read from // - out - PTY output handle that we will write to // - signal - PTY signal handle for out of band messaging -// - process - Process handle to client so we can track its lifetime and exit appropriately +// - ref - Client reference handle for console session so it stays alive until we let go +// - server - PTY process handle to track for lifetime/cleanup +// - client - Process handle to client so we can track its lifetime and exit appropriately // Return Value: // - E_NOT_VALID_STATE if a event handler is not registered before calling. `::DuplicateHandle` // error codes if we cannot manage to make our own copy of handles to retain. Or S_OK/error // from the registered handler event function. -HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE process) noexcept +HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept { // Report an error if no one registered a handoff function before calling this. RETURN_HR_IF_NULL(E_NOT_VALID_STATE, _pfnHandoff); @@ -101,8 +103,10 @@ HRESULT CTerminalHandoff::EstablishPtyHandoff(HANDLE in, HANDLE out, HANDLE sign RETURN_IF_FAILED(_duplicateHandle(in, in)); RETURN_IF_FAILED(_duplicateHandle(out, out)); RETURN_IF_FAILED(_duplicateHandle(signal, signal)); - RETURN_IF_FAILED(_duplicateHandle(process, process)); + RETURN_IF_FAILED(_duplicateHandle(ref, ref)); + RETURN_IF_FAILED(_duplicateHandle(server, server)); + RETURN_IF_FAILED(_duplicateHandle(client, client)); // Call registered handler from when we started listening. - return _pfnHandoff(in, out, signal, process); + return _pfnHandoff(in, out, signal, ref, server, client); } diff --git a/src/cascadia/TerminalConnection/CTerminalHandoff.h b/src/cascadia/TerminalConnection/CTerminalHandoff.h index 396f4a095..2e173e0a3 100644 --- a/src/cascadia/TerminalConnection/CTerminalHandoff.h +++ b/src/cascadia/TerminalConnection/CTerminalHandoff.h @@ -26,7 +26,7 @@ Author(s): #define __CLSID_CTerminalHandoff "051F34EE-C1FD-4B19-AF75-9BA54648434C" #endif -using NewHandoffFunction = HRESULT (*)(HANDLE, HANDLE, HANDLE, HANDLE); +using NewHandoffFunction = HRESULT (*)(HANDLE, HANDLE, HANDLE, HANDLE, HANDLE, HANDLE); struct __declspec(uuid(__CLSID_CTerminalHandoff)) CTerminalHandoff : public Microsoft::WRL::RuntimeClass, ITerminalHandoff> @@ -35,7 +35,9 @@ struct __declspec(uuid(__CLSID_CTerminalHandoff)) STDMETHODIMP EstablishPtyHandoff(HANDLE in, HANDLE out, HANDLE signal, - HANDLE process) noexcept override; + HANDLE ref, + HANDLE server, + HANDLE client) noexcept override; #pragma endregion diff --git a/src/cascadia/TerminalConnection/ConptyConnection.cpp b/src/cascadia/TerminalConnection/ConptyConnection.cpp index a88fa2ee6..036dccb49 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.cpp +++ b/src/cascadia/TerminalConnection/ConptyConnection.cpp @@ -194,6 +194,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation ConptyConnection::ConptyConnection(const HANDLE hSig, const HANDLE hIn, const HANDLE hOut, + const HANDLE hRef, + const HANDLE hServerProcess, const HANDLE hClientProcess) : _initialRows{ 25 }, _initialCols{ 80 }, @@ -208,7 +210,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation _inPipe{ hIn }, _outPipe{ hOut } { - hSig; // TODO: GH 9464 this needs to be packed into the hpcon + THROW_IF_FAILED(ConptyPackPseudoConsole(hServerProcess, hRef, hSig, &_hPC)); if (_guid == guid{}) { _guid = Utils::CreateGuid(); @@ -500,10 +502,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation winrt::event_token ConptyConnection::NewConnection(NewConnectionHandler const& handler) { return _newConnectionHandlers.add(handler); }; void ConptyConnection::NewConnection(winrt::event_token const& token) { _newConnectionHandlers.remove(token); }; - HRESULT ConptyConnection::NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE process) noexcept + HRESULT ConptyConnection::NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept try { - auto conn = winrt::make(signal, in, out, process); + auto conn = winrt::make(signal, in, out, ref, server, client); _newConnectionHandlers(conn); return S_OK; diff --git a/src/cascadia/TerminalConnection/ConptyConnection.h b/src/cascadia/TerminalConnection/ConptyConnection.h index 3eee9fc8d..f53b4a56e 100644 --- a/src/cascadia/TerminalConnection/ConptyConnection.h +++ b/src/cascadia/TerminalConnection/ConptyConnection.h @@ -22,6 +22,8 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation ConptyConnection(const HANDLE hSig, const HANDLE hIn, const HANDLE hOut, + const HANDLE hRef, + const HANDLE hServerProcess, const HANDLE hClientProcess); ConptyConnection( @@ -54,7 +56,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation void _indicateExitWithStatus(unsigned int status) noexcept; void _ClientTerminated() noexcept; - static HRESULT NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE process) noexcept; + static HRESULT NewHandoff(HANDLE in, HANDLE out, HANDLE signal, HANDLE ref, HANDLE server, HANDLE client) noexcept; uint32_t _initialRows{}; uint32_t _initialCols{}; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 12b19da1f..639c52c85 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -1664,7 +1664,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation void TermControl::_CursorPositionChanged(const IInspectable& /*sender*/, const IInspectable& /*args*/) { - _tsfTryRedrawCanvas->Run(); + if (_tsfTryRedrawCanvas) + { + _tsfTryRedrawCanvas->Run(); + } } hstring TermControl::Title() diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index d043e5472..19fc4381e 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -89,7 +89,7 @@ - + diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index 6e4e24bda..e053425cb 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -640,6 +640,14 @@ void AppHost::_BecomeMonarch(const winrt::Windows::Foundation::IInspectable& /*s const winrt::Windows::Foundation::IInspectable& /*args*/) { _setupGlobalHotkeys(); + + // The monarch is just going to be THE listener for inbound connections. + _listenForInboundConnections(); +} + +void AppHost::_listenForInboundConnections() +{ + _logic.SetInboundListener(); } winrt::fire_and_forget AppHost::_setupGlobalHotkeys() diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index 712b757ef..4adac9a80 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -74,6 +74,7 @@ private: bool _LazyLoadDesktopManager(); + void _listenForInboundConnections(); winrt::fire_and_forget _setupGlobalHotkeys(); winrt::fire_and_forget _createNewTerminalWindow(winrt::Microsoft::Terminal::Settings::Model::GlobalSummonArgs args); void _HandleSettingsChanged(const winrt::Windows::Foundation::IInspectable& sender, diff --git a/src/host/proxy/ITerminalHandoff.idl b/src/host/proxy/ITerminalHandoff.idl index 79c356908..d06799658 100644 --- a/src/host/proxy/ITerminalHandoff.idl +++ b/src/host/proxy/ITerminalHandoff.idl @@ -6,11 +6,13 @@ import "ocidl.idl"; [ object, - uuid(FA1E3AB4-9AEC-4A3C-96CA-E6078C30BD74) + uuid(59D55CCE-FC8A-48B4-ACE8-0A9286C6557F) ] interface ITerminalHandoff : IUnknown { HRESULT EstablishPtyHandoff([in, system_handle(sh_pipe)] HANDLE in, [in, system_handle(sh_pipe)] HANDLE out, [in, system_handle(sh_pipe)] HANDLE signal, + [in, system_handle(sh_file)] HANDLE ref, + [in, system_handle(sh_process)] HANDLE server, [in, system_handle(sh_process)] HANDLE client); }; diff --git a/src/host/srvinit.cpp b/src/host/srvinit.cpp index 94799aa0d..c5283dca4 100644 --- a/src/host/srvinit.cpp +++ b/src/host/srvinit.cpp @@ -14,6 +14,7 @@ #include "../types/inc/GlyphWidth.hpp" +#include "../server/DeviceHandle.h" #include "../server/Entrypoints.h" #include "../server/IoSorter.h" @@ -414,6 +415,14 @@ try wil::unique_handle clientProcess{ OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, TRUE, static_cast(connectMessage->Descriptor.Process)) }; RETURN_LAST_ERROR_IF_NULL(clientProcess.get()); + wil::unique_handle refHandle; + RETURN_IF_NTSTATUS_FAILED(DeviceHandle::CreateClientHandle(refHandle.addressof(), + Server, + L"\\Reference", + FALSE)); + + const auto serverProcess = GetCurrentProcess(); + ::Microsoft::WRL::ComPtr handoff; RETURN_IF_FAILED(CoCreateInstance(g.handoffTerminalClsid.value(), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&handoff))); @@ -421,6 +430,8 @@ try RETURN_IF_FAILED(handoff->EstablishPtyHandoff(inPipeTheirSide.get(), outPipeTheirSide.get(), signalPipeTheirSide.get(), + refHandle.get(), + serverProcess, clientProcess.get())); inPipeTheirSide.release(); diff --git a/src/inc/conpty-static.h b/src/inc/conpty-static.h index a88572f31..bd3ac59ca 100644 --- a/src/inc/conpty-static.h +++ b/src/inc/conpty-static.h @@ -24,6 +24,8 @@ HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size); VOID WINAPI ConptyClosePseudoConsole(HPCON hPC); +HRESULT WINAPI ConptyPackPseudoConsole(HANDLE hServerProcess, HANDLE hRef, HANDLE hSignal, HPCON* phPC); + #ifdef __cplusplus } #endif diff --git a/src/winconpty/winconpty.cpp b/src/winconpty/winconpty.cpp index fbf7fa13b..5ef632955 100644 --- a/src/winconpty/winconpty.cpp +++ b/src/winconpty/winconpty.cpp @@ -400,4 +400,35 @@ extern "C" VOID WINAPI ConptyClosePseudoConsole(_In_ HPCON hPC) } } +// NOTE: This one is not defined in the Windows headers but is +// necessary for our outside recipient in the Terminal +// to set up a PTY session in fundamentally the same way as the +// Creation functions. Using the same HPCON pack enables +// resizing and closing to "just work." + +// Function Description: +// Packs loose handle information for an inbound ConPTY +// session into the same HPCON as a created session. +extern "C" HRESULT WINAPI ConptyPackPseudoConsole(_In_ HANDLE hProcess, + _In_ HANDLE hRef, + _In_ HANDLE hSignal, + _Out_ HPCON* phPC) +{ + RETURN_HR_IF(E_INVALIDARG, nullptr == phPC); + *phPC = nullptr; + RETURN_HR_IF(E_INVALIDARG, !_HandleIsValid(hProcess)); + RETURN_HR_IF(E_INVALIDARG, !_HandleIsValid(hRef)); + RETURN_HR_IF(E_INVALIDARG, !_HandleIsValid(hSignal)); + + PseudoConsole* pPty = (PseudoConsole*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PseudoConsole)); + RETURN_IF_NULL_ALLOC(pPty); + + pPty->hConPtyProcess = hProcess; + pPty->hPtyReference = hRef; + pPty->hSignal = hSignal; + + *phPC = (HPCON)pPty; + return S_OK; +} + #pragma warning(pop)