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

Add support for terminal handoff #716

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 12 additions & 2 deletions binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@
'AdditionalOptions': [
'/guard:cf'
]
}
},
'VCMIDLTool': {
'OutputDirectory': '<(INTERMEDIATE_DIR)',
'HeaderFileName': '<(RULE_INPUT_ROOT).h',
},
},
}],
],
Expand All @@ -33,7 +37,13 @@
'target_name': 'conpty',
'sources' : [
'src/win/conpty.cc',
'src/win/path_util.cc'
'src/win/path_util.cc',
'src/win/ITerminalHandoff.idl'
],
'include_dirs' : [
'deps/wil/include',
# To allow including "ITerminalHandoff.h"
'<(INTERMEDIATE_DIR)',
],
'libraries': [
'shlwapi.lib'
Expand Down
82 changes: 82 additions & 0 deletions src/win/ITerminalHandoff.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

import "unknwn.idl";

typedef struct _TERMINAL_STARTUP_INFO
{
// In STARTUPINFO
BSTR pszTitle;

// Also wanted
BSTR pszIconPath;
LONG iconIndex;

// The rest of STARTUPINFO
DWORD dwX;
DWORD dwY;
DWORD dwXSize;
DWORD dwYSize;
DWORD dwXCountChars;
DWORD dwYCountChars;
DWORD dwFillAttribute;
DWORD dwFlags;
WORD wShowWindow;
} TERMINAL_STARTUP_INFO;

// LOAD BEARING!
//
// There is only ever one OpenConsoleProxy.dll loaded by COM for _ALL_ terminal
// instances, across Dev, Preview, Stable, whatever. So we have to keep all old
// versions of interfaces in the file here, even if the old version is no longer
// in use.

// This was the original prototype. The reasons for changes to it are explained below.
[
object,
uuid(59D55CCE-FC8A-48B4-ACE8-0A9286C6557F)
] interface ITerminalHandoff : IUnknown
{
// DEPRECATED!
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);
};

// We didn't consider the need for TERMINAL_STARTUP_INFO, because Windows Terminal never had support for launching
// .lnk files in the first place. They aren't executables after all, but rather a shell thing.
// Its need quickly became apparent right after releasing ITerminalHandoff, which is why it was very short-lived.
[
object,
uuid(AA6B364F-4A50-4176-9002-0AE755E7B5EF)
] interface ITerminalHandoff2 : 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,
[in] TERMINAL_STARTUP_INFO startupInfo);
};

// Quite a while later, we realized that passing the in/out handles as [in] instead of [out] has always been flawed.
// It prevents the terminal from making choices over the pipe buffer size and whether to use overlapped IO or not.
// The other handles remain [in] parameters because they have always been created internally by ConPTY.
// Additionally, passing TERMINAL_STARTUP_INFO by-value was unusual under COM as structs are usually given by-ref.
[
object,
uuid(6F23DA90-15C5-4203-9DB0-64E73F1B1B00)
] interface ITerminalHandoff3 : IUnknown
{
HRESULT EstablishPtyHandoff([out, system_handle(sh_pipe)] HANDLE* in,
[out, system_handle(sh_pipe)] HANDLE* out,
[in, system_handle(sh_pipe)] HANDLE signal,
[in, system_handle(sh_file)] HANDLE reference,
[in, system_handle(sh_process)] HANDLE server,
[in, system_handle(sh_process)] HANDLE client,
[in] const TERMINAL_STARTUP_INFO* startupInfo);
};
57 changes: 57 additions & 0 deletions src/win/conpty.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
#include <vector>
#include <Windows.h>
#include <strsafe.h>
#include <wrl.h>
#include "ITerminalHandoff.h"
#include "path_util.h"
#include "conpty.h"

Expand Down Expand Up @@ -542,16 +544,71 @@ static Napi::Value PtyKill(const Napi::CallbackInfo& info) {
return env.Undefined();
}

class TerminalHandoff
: public Microsoft::WRL::RuntimeClass<
Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>,
ITerminalHandoff2>
{
public:
virtual HRESULT STDMETHODCALLTYPE EstablishPtyHandoff(
/* [system_handle][in] */ HANDLE in,
/* [system_handle][in] */ HANDLE out,
/* [system_handle][in] */ HANDLE signal,
/* [system_handle][in] */ HANDLE ref,
/* [system_handle][in] */ HANDLE server,
/* [system_handle][in] */ HANDLE client,
/* [in] */ TERMINAL_STARTUP_INFO startupInfo) override
{
MessageBoxW(nullptr, L"EstablishPtyHandoff", L"", 0);
return E_NOTIMPL;
}
};

static DWORD g_cTerminalHandoffRegistration = 0;

static Napi::Value PtyRegisterHandoff(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);

if (info.Length() != 2 ||
!info[0].IsString() ||
!info[1].IsFunction()) {
throw Napi::Error::New(env, "Usage: pty.registerHandoff(clsid, callback)");
}

const std::wstring clsidStr(path_util::to_wstring(info[0].As<Napi::String>()));
Napi::Function callback = info[1].As<Napi::Function>();

using namespace Microsoft::WRL;

const auto classFactory = Make<SimpleClassFactory<TerminalHandoff>>();

ComPtr<IUnknown> unk;
HRESULT hr = classFactory.As(&unk);

CLSID clsid;
hr = CLSIDFromString(clsidStr.c_str(), &clsid);

hr = CoRegisterClassObject(clsid, unk.Get(), CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &g_cTerminalHandoffRegistration);

CoAddRefServerProcess();

return env.Undefined();
}

/**
* Init
*/

Napi::Object init(Napi::Env env, Napi::Object exports) {
MessageBoxW(nullptr, L"", L"", 0); // debug

exports.Set("startProcess", Napi::Function::New(env, PtyStartProcess));
exports.Set("connect", Napi::Function::New(env, PtyConnect));
exports.Set("resize", Napi::Function::New(env, PtyResize));
exports.Set("clear", Napi::Function::New(env, PtyClear));
exports.Set("kill", Napi::Function::New(env, PtyKill));
exports.Set("registerHandoff", Napi::Function::New(env, PtyRegisterHandoff));
return exports;
};

Expand Down
Loading