Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable the Terminal to tell ConPTY who the owner is #12526

Merged
merged 18 commits into from
Apr 12, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/actions/spelling/allow/apis.txt
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ REGCLS
RETURNCMD
rfind
roundf
ROOTOWNER
RSHIFT
SACL
schandle
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalApp/AppLogic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1641,4 +1641,5 @@ namespace winrt::TerminalApp::implementation
{
return _settings.GlobalSettings().ShowTitleInTitlebar();
}

DHowett marked this conversation as resolved.
Show resolved Hide resolved
}
11 changes: 11 additions & 0 deletions src/cascadia/TerminalApp/TerminalPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ namespace winrt::TerminalApp::implementation

// Method Description:
// - implements the IInitializeWithWindow interface from shobjidl_core.
// - We're going to use this HWND as the owner for the ConPTY windows, via
// ConptyConnection::ReparentWindow. We need this for applications that
// call GetConsoleWindow, and attempt to open a MessageBox for the
// console. By marking the conpty windows as owned by the Terminal HWND,
// the message box will be owned by the Terminal window as well.
// - see GH#2988
HRESULT TerminalPage::Initialize(HWND hwnd)
{
_hostingHwnd = hwnd;
Expand Down Expand Up @@ -2424,6 +2430,11 @@ namespace winrt::TerminalApp::implementation
// create here.
// TermControl will copy the settings out of the settings passed to it.
TermControl term{ settings.DefaultSettings(), settings.UnfocusedSettings(), connection };

if (_hostingHwnd.has_value())
{
term.OwningHwnd(reinterpret_cast<uint64_t>(*_hostingHwnd));
}
return term;
}

Expand Down
23 changes: 23 additions & 0 deletions src/cascadia/TerminalConnection/ConptyConnection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,12 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}

THROW_IF_FAILED(_CreatePseudoConsoleAndPipes(dimensions, flags, &_inPipe, &_outPipe, &_hPC));

if (_initialParentHwnd != 0)
{
THROW_IF_FAILED(ConptyReparentPseudoConsole(_hPC.get(), reinterpret_cast<HWND>(_initialParentHwnd)));
}

THROW_IF_FAILED(_LaunchAttachedClient());
}
// But if it was an inbound handoff... attempt to synchronize the size of it with what our connection
Expand All @@ -327,6 +333,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance));

THROW_IF_FAILED(ConptyResizePseudoConsole(_hPC.get(), dimensions));
THROW_IF_FAILED(ConptyReparentPseudoConsole(_hPC.get(), reinterpret_cast<HWND>(_initialParentHwnd)));
}

_startTime = std::chrono::high_resolution_clock::now();
Expand Down Expand Up @@ -482,6 +489,22 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
}
}

void ConptyConnection::ReparentWindow(const uint64_t newParent)
{
// If we haven't started connecting at all, stash this HWND to use once we have started.
if (!_isStateAtOrBeyond(ConnectionState::Connecting))
{
_initialParentHwnd = newParent;
}
// Otherwise, just inform the conpty of the new owner window handle.
// This shouldn't be hittable until GH#5000 / GH#1256, when it's
// possible to reparent terminals to different windows.
else if (_isConnected())
{
THROW_IF_FAILED(ConptyReparentPseudoConsole(_hPC.get(), reinterpret_cast<HWND>(newParent)));
}
}

void ConptyConnection::Close() noexcept
try
{
Expand Down
2 changes: 2 additions & 0 deletions src/cascadia/TerminalConnection/ConptyConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
void Resize(uint32_t rows, uint32_t columns);
void Close() noexcept;
void ClearBuffer();
void ReparentWindow(const uint64_t newParent);

winrt::guid Guid() const noexcept;
winrt::hstring Commandline() const;
Expand Down Expand Up @@ -65,6 +66,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation

uint32_t _initialRows{};
uint32_t _initialCols{};
uint64_t _initialParentHwnd{ 0 };
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
hstring _commandline{};
hstring _startingDirectory{};
hstring _startingTitle{};
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalConnection/ConptyConnection.idl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace Microsoft.Terminal.TerminalConnection
Guid Guid { get; };
String Commandline { get; };
void ClearBuffer();
void ReparentWindow(UInt64 newParent);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mind filing a followup to make this and the following ones about focus part of a new interface? ITerminalConnectionWindowHosting or something


static event NewConnectionHandler NewConnection;
static void StartInboundListener();
Expand Down
8 changes: 8 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const auto height = vp.Height();
_connection.Resize(height, width);

if (_OwningHwnd != 0)
{
if (auto conpty{ _connection.try_as<TerminalConnection::ConptyConnection>() })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't love this coupling, thus the interface :)

{
conpty.ReparentWindow(_OwningHwnd);
}
}

// Override the default width and height to match the size of the swapChainPanel
_settings->InitialCols(width);
_settings->InitialRows(height);
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation

void AdjustOpacity(const double opacity, const bool relative);

// TODO:GH#1256 - When a tab can be torn out or otherwise reparented to
// another window, this value will need a custom setter, so that we can
// also update the connection.
WINRT_PROPERTY(uint64_t, OwningHwnd, 0);

RUNTIME_SETTING(double, Opacity, _settings->Opacity());
RUNTIME_SETTING(bool, UseAcrylic, _settings->UseAcrylic());

Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalControl/ICoreState.idl
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@ namespace Microsoft.Terminal.Control
Microsoft.Terminal.TerminalConnection.ConnectionState ConnectionState { get; };

Microsoft.Terminal.Core.Scheme ColorScheme { get; set; };

UInt64 OwningHwnd;

};
}
10 changes: 10 additions & 0 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2777,4 +2777,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}

void TermControl::OwningHwnd(uint64_t owner)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: vaguely prefer the HostingWindow parlance; we can take that as a followup (look at TSE's IHostedInWindow)

{
_core.OwningHwnd(owner);
}

uint64_t TermControl::OwningHwnd()
{
return _core.OwningHwnd();
}

}
3 changes: 3 additions & 0 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
bool BracketedPasteEnabled() const noexcept;

double BackgroundOpacity() const;

uint64_t OwningHwnd();
void OwningHwnd(uint64_t owner);
#pragma endregion

void ScrollViewport(int viewTop);
Expand Down
5 changes: 5 additions & 0 deletions src/cascadia/WindowsTerminal/IslandWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ IslandWindow::~IslandWindow()
_source.Close();
}

HWND IslandWindow::GetInteropHandle() const
{
return _interopWindowHandle;
}

// Method Description:
// - Create the actual window that we'll use for the application.
// Arguments:
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/WindowsTerminal/IslandWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class IslandWindow :
virtual void MakeWindow() noexcept;
void Close();
virtual void OnSize(const UINT width, const UINT height);
HWND GetInteropHandle() const;

[[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override;
void OnResize(const UINT width, const UINT height) override;
Expand Down
58 changes: 58 additions & 0 deletions src/host/PtySignalInputThread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ void PtySignalInputThread::ConnectConsole() noexcept
{
_DoResizeWindow(*_earlyResize);
}
if (_earlyReparent)
{
_DoSetWindowParent(*_earlyReparent);
}
}

// Method Description:
Expand Down Expand Up @@ -119,6 +123,28 @@ void PtySignalInputThread::ConnectConsole() noexcept

break;
}
case PtySignal::SetParent:
{
SetParentData reparentMessage = { 0 };
_GetData(&reparentMessage, sizeof(reparentMessage));

LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });

// If the client app hasn't yet connected, stash the new owner.
// We'll later (PtySignalInputThread::ConnectConsole) use the value
// to set up the owner of the conpty window.
if (!_consoleConnected)
{
_earlyReparent = reparentMessage;
}
else
{
_DoSetWindowParent(reparentMessage);
}

break;
}
default:
{
THROW_HR(E_UNEXPECTED);
Expand Down Expand Up @@ -147,6 +173,38 @@ void PtySignalInputThread::_DoClearBuffer()
_pConApi->ClearBuffer();
}

// Method Description:
// - Update the owner of the pseudo-window we're using for the conpty HWND. This
// allows to mark the pseudoconsole windows as "owner" by the terminal HWND
// that's actually hosting them.
// - Refer to GH#2988
// Arguments:
// - data - Packet information containing owner HWND information
// Return Value:
// - <none>
void PtySignalInputThread::_DoSetWindowParent(const SetParentData& data)
{
_pConApi->ReparentWindow(data.handle);
}

// Method Description:
// - If we were given a owner HWND, then manually start the pseudo window now.
// We need to do this specifically on the thread with the message pump. If the
// window is created on another thread, then the window won't have a message
// pump associated with it, and a DPI change in the connected terminal could
// end up HANGING THE CONPTY (for example).
// Arguments:
// - <none>
// Return Value:
// - <none>
void PtySignalInputThread::StartPseudoWindowIfNeeded()
{
if (_earlyReparent.has_value())
{
ServiceLocator::LocatePseudoWindow(reinterpret_cast<HWND>(_earlyReparent.value().handle));
}
}
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved

// Method Description:
// - Retrieves bytes from the file stream and exits or throws errors should the pipe state
// be compromised.
Expand Down
10 changes: 10 additions & 0 deletions src/host/PtySignalInputThread.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ namespace Microsoft::Console
PtySignalInputThread& operator=(const PtySignalInputThread&) = delete;

void ConnectConsole() noexcept;
void StartPseudoWindowIfNeeded();

private:
enum class PtySignal : unsigned short
{
ClearBuffer = 2,
SetParent = 3,
ResizeWindow = 8
};

Expand All @@ -49,10 +51,15 @@ namespace Microsoft::Console
unsigned short sx;
unsigned short sy;
};
struct SetParentData
{
uint64_t handle;
};
Comment on lines +53 to +56
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why's this a struct? Will there be more members in the future?
Why is handle not a HWND?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meh, they're all structs? I thought it was a nice abstraction to indicate "this is the packet of data that's sent". I don't expect to add more members in the future, but who knows.

As far as the uint64_t vs HWND - IIRC HWND is technically just a void*, so I wanted to be explicit with the size of data that's getting passed on the wire here. It's the same idea as like, every time we use a uint64_t for a HWND in any WinRT things.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think transmitting it as a unintptr_t would've been more consistent with our other Win32 APIs (which this API is closest to IMO).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, this is a wire format. 32-bit VSCode might be using 64-bit conhost (in fact: it is using 64-bit conhost). uintptr_t changes size. uint64_t does not.


[[nodiscard]] HRESULT _InputThread();
bool _GetData(_Out_writes_bytes_(cbBuffer) void* const pBuffer, const DWORD cbBuffer);
void _DoResizeWindow(const ResizeWindowData& data);
void _DoSetWindowParent(const SetParentData& data);
void _DoClearBuffer();
void _Shutdown();

Expand All @@ -62,5 +69,8 @@ namespace Microsoft::Console
bool _consoleConnected;
std::optional<ResizeWindowData> _earlyResize;
std::unique_ptr<Microsoft::Console::VirtualTerminal::ConGetSet> _pConApi;

public:
std::optional<SetParentData> _earlyReparent;
};
}
6 changes: 6 additions & 0 deletions src/host/VtIo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,12 @@ bool VtIo::IsUsingVt() const

if (_pPtySignalInputThread)
{
// IMPORTANT! Start the pseudo window on this thread. This thread has a
// message pump. If you DON'T, then a DPI change in the owning hwnd will
// cause us to get a dpi change as well, which we'll never deque and
// handle, effectively HANGING THE OWNER HWND.
_pPtySignalInputThread->StartPseudoWindowIfNeeded();

// Let the signal thread know that the console is connected
_pPtySignalInputThread->ConnectConsole();
}
Expand Down
14 changes: 14 additions & 0 deletions src/host/outputStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -892,3 +892,17 @@ void ConhostInternalGetSet::UpdateSoftFont(const gsl::span<const uint16_t> bitPa
pRender->UpdateSoftFont(bitPattern, cellSize, centeringHint);
}
}

void ConhostInternalGetSet::ReparentWindow(const uint64_t handle)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting -- why did we keep it a uint64_t this deep in the stack? We should decap it as early as we can

{
// This will initialize s_interactivityFactory for us. It will also
// conveniently return 0 when we're on OneCore.
zadjii-msft marked this conversation as resolved.
Show resolved Hide resolved
//
// If the window hasn't been created yet, by some other call to
// LocatePseudoWindow, then this will also initialize the owner of the
// window.
if (const auto psuedoHwnd{ ServiceLocator::LocatePseudoWindow(reinterpret_cast<HWND>(handle)) })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

psuedo -> pseudo

{
LOG_LAST_ERROR_IF_NULL(::SetParent(psuedoHwnd, reinterpret_cast<HWND>(handle)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just making sure . . . so this sets the OWNER, not the PARENT, for the same reason as below (no CHILDWINDOW)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly. There's no ::SetOwner, SetParent sets the owner/parent based on if WS_CHILD is set.

}
}
2 changes: 2 additions & 0 deletions src/host/outputStream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal::
const SIZE cellSize,
const size_t centeringHint) override;

void ReparentWindow(const uint64_t handle);

private:
void _modifyLines(const size_t count, const bool insert);

Expand Down
2 changes: 2 additions & 0 deletions src/inc/conpty-static.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size);

HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC);

HRESULT WINAPI ConptyReparentPseudoConsole(HPCON hPC, HWND newParent);

VOID WINAPI ConptyClosePseudoConsole(HPCON hPC);

HRESULT WINAPI ConptyPackPseudoConsole(HANDLE hServerProcess, HANDLE hRef, HANDLE hSignal, HPCON* phPC);
Expand Down
Loading