Re: dynamic conpty loading
Takashi Yano via Cygwin <[email protected]>
| Newsgroups | gmane.os.cygwin |
|---|---|
| Message-ID | <[email protected]> |
On Thu, 27 Nov 2025 17:20:26 +0900 Takashi Yano via Cygwin <[email protected]> wrote: > On Wed, 26 Nov 2025 19:50:07 +0100 > Thomas Wolff wrote: > > > > Am 26.11.2025 um 11:32 schrieb Takashi Yano via Cygwin: > > > On Fri, 21 Nov 2025 17:58:01 +0100 > > > Thomas Wolff wrote: > > > > > >> Am 21.11.2025 um 17:46 schrieb Corinna Vinschen via Cygwin: > > >>> Hi Thomas, > > >>> > > >>> On Nov 21 16:46, Thomas Wolff via Cygwin wrote: > > >>>> Am 21.11.2025 um 11:04 schrieb Takashi Yano via Cygwin: > > >>>>> On Sun, 5 Oct 2025 10:15:55 +0200 > > >>>>> Thomas Wolff wrote: > > >>>>>>>> The procedure seems to be: > > >>>>>>>> try LoadLibrary("conpty.dll"), (if not successful, fallback to > > >>>>>>>> GetModuleHandle("kernel32") instead) to retrieve GetProcAddress for > > >>>>>>>> CreatePseudoConsole, ResizePseudoConsole, ClosePseudoConsole, and > > >>>>>>>> then somehow (?) use those while calling CreateProcess. > > >>>>>>>> The latter is woven into the cygwin library and I don't think it's a > > >>>>>>>> good idea to clone that out of cygwin for a patched process creation > > >>>>>>>> in mintty. > > >>>>>>>> > > >>>>>>>> My question/suggestion: > > >>>>>>>> Can a cygwin mode switch to a selected conpty library instead of the > > >>>>>>>> default one please? > > >>> Where do you expect this conpty.dll to reside? > > >>> > > >>> If we do something like that, I would suggest to make sure that this > > >>> conpty.dll is in a well-defined place. /bin or /lib might be a good > > >>> idea. And if it exists in this defined place, it will be used by Cygwin. > > >>> > > >>> We can tweak the autoload mechanism to allow a primary DLL and a fallback > > >>> DLL, that drops the need to set the CYWIN env var. > > >>> > > >>> The mintty package could contain a post-install script, or some other > > >>> script in /bin to allow a user with admin rights downloading the latest > > >>> (or the most sensible) conpty.dll from MSFT. > > >>> > > >>> Would that make sense? > > >>> > > >>> > > >>> Corinna > > >>> > > >> That makes much sense, yes, thank you. /bin/conpty.dll would probably be > > >> a good place. > > > I checked the source of conpty.dll > > > https://github.com/microsoft/terminal/blob/main/src/winconpty/winconpty.cpp > > > > > > conpty.dll seems to launch OpenConsole.exe in the directory where > > > WindowsTerminal is installed. > > > However, BUILTIN\Users does not have permission to execute OpenConsole.exe. > > > > > > Do you have some idea how to solve this problem? > > > WindowsTerminal itself can launch OpenConsole.exe, so there should be > > > some solutions, I think. > > Did you install the nuget package? It can be extracted with zip, so you > > can install OpenConsole as a normal program... > > I did not find a way, though, to invoke it explicitly so that it would > > solve the pty interworking problems. > > Thanks. > > I've made an experimental patch against fhandler/pty.cc and > draft package named openconsole that installs official OpenConsole.exe > binary into /usr/bin. > > I confirmed the patch with openconsole package enables the mouse > support for vim91 and neovim 0.8.0 (native windows binary) in > pseudo console. pty patch and openconsole package files are revised. > Any comments and suggestions would be appreciated. -- Takashi Yano <[email protected]> -- Problem reports: https://cygwin.com/problems.html FAQ: https://cygwin.com/faq/ Documentation: https://cygwin.com/docs.html Unsubscribe info: https://cygwin.com/ml/#unsubscribe-simple
v2-0001-Cygwin-pty-Experimental-OpenConsole.exe-support.patch
(text/plain, 13.1 KB)
From 28570bd464446eb327b588b623f70f8cf8f46b3d Mon Sep 17 00:00:00 2001 From: Takashi Yano <[email protected]> Date: Thu, 27 Nov 2025 15:52:12 +0900 Subject: [PATCH v2] Cygwin: pty: Experimental OpenConsole.exe support Signed-off-by: Takashi Yano <[email protected]> --- winsup/cygwin/fhandler/pty.cc | 281 ++++++++++++++++++++++++++++++---- 1 file changed, 251 insertions(+), 30 deletions(-) diff --git a/winsup/cygwin/fhandler/pty.cc b/winsup/cygwin/fhandler/pty.cc index 679068ea2..2c9af1742 100644 --- a/winsup/cygwin/fhandler/pty.cc +++ b/winsup/cygwin/fhandler/pty.cc @@ -33,6 +33,136 @@ details. */ #define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE 0x00020016 #endif /* PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE */ +static NTSTATUS +create_handle (PHANDLE handle, PCWSTR device_name, + ACCESS_MASK desired_access, HANDLE parent, + BOOLEAN inheritable, ULONG open_options) +{ + ULONG flags = OBJ_CASE_INSENSITIVE; + if (inheritable) + flags |= OBJ_INHERIT; + + UNICODE_STRING name; + RtlInitUnicodeString (&name, device_name); + + OBJECT_ATTRIBUTES object_attributes; + InitializeObjectAttributes (&object_attributes, &name, flags, parent, NULL); + + IO_STATUS_BLOCK io; + return NtOpenFile (handle, desired_access, &object_attributes, &io, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + open_options); +} + +extern "C" WINBASEAPI HRESULT WINAPI +CreatePseudoConsole_new (COORD size, HANDLE h_input, HANDLE h_output, + DWORD flags, HPCON *hpcon) +{ + + HANDLE h_con_server, h_con_reference; + NTSTATUS status; + BOOL res; + HANDLE h_read_pipe, h_write_pipe; + BOOL inherit_cursor; + path_conv conhost ("/usr/bin/OpenConsole.exe"); + size_t len; + HANDLE inherited_handles[4]; + STARTUPINFOEXW si = {0, }; + PROCESS_INFORMATION pi; + SIZE_T list_size = 0; + LPPROC_THREAD_ATTRIBUTE_LIST attr_list; + HPCON_INTERNAL *hpcon_internal; + + status = create_handle (&h_con_server, L"\\Device\\ConDrv\\Server", + GENERIC_ALL, NULL, TRUE, 0); + if (!NT_SUCCESS (status)) + goto cleanup; + status = create_handle (&h_con_reference, L"\\Reference", + GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, + h_con_server, FALSE, FILE_SYNCHRONOUS_IO_NONALERT); + if (!NT_SUCCESS (status)) + goto cleanup_h_con_server; + + res = CreatePipe (&h_read_pipe, &h_write_pipe, &sec_none, 0); + if (!res) + goto cleanup_h_con_reference; + res = SetHandleInformation (h_read_pipe, + HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); + if (!res) + goto cleanup_pipe; + + inherit_cursor = (flags & PSEUDOCONSOLE_INHERIT_CURSOR) ? TRUE : FALSE; + + WCHAR cmd[MAX_PATH]; + len = conhost.get_wide_win32_path_len (); + conhost.get_wide_win32_path (cmd); + __small_swprintf (cmd + len, + L" --headless %W" + "--width %d --height %d --signal 0x%x --server 0x%x", + inherit_cursor ? L"--inheritcursor " : L"", + size.X, size.Y, h_read_pipe, h_con_server); + + si.StartupInfo.cb = sizeof (STARTUPINFOEXW); + si.StartupInfo.hStdInput = h_input; + si.StartupInfo.hStdOutput = h_output; + si.StartupInfo.hStdError = h_output; + si.StartupInfo.dwFlags |= STARTF_USESTDHANDLES; + + inherited_handles[0] = h_con_server; + inherited_handles[1] = h_input; + inherited_handles[2] = h_output; + inherited_handles[3] = h_read_pipe; + + InitializeProcThreadAttributeList (NULL, 1, 0, &list_size); + attr_list = + (LPPROC_THREAD_ATTRIBUTE_LIST) HeapAlloc (GetProcessHeap (), 0, list_size); + if (!attr_list) + goto cleanup_pipe; + + si.lpAttributeList = attr_list; + InitializeProcThreadAttributeList (si.lpAttributeList, 1, 0, &list_size); + UpdateProcThreadAttribute (si.lpAttributeList, 0, + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, + inherited_handles, sizeof (inherited_handles), + NULL, NULL); + + + res = CreateProcessW (NULL, cmd, NULL, NULL, + TRUE, EXTENDED_STARTUPINFO_PRESENT, + NULL, NULL, &si.StartupInfo, &pi); + if (!res) + goto cleanup_heap; + + hpcon_internal = (HPCON_INTERNAL *) + HeapAlloc (GetProcessHeap (), 0, sizeof (HPCON_INTERNAL)); + if (!hpcon_internal) + goto cleanup_heap; + hpcon_internal->hWritePipe = h_write_pipe; + hpcon_internal->hConDrvReference = h_con_reference; + hpcon_internal->hConHostProcess = pi.hProcess; + *hpcon = (HPCON) hpcon_internal; + + HeapFree (GetProcessHeap(), 0, attr_list); + CloseHandle (h_con_server); + CloseHandle (pi.hThread); + CloseHandle (pi.hProcess); + + return S_OK; + +cleanup_heap: + HeapFree (GetProcessHeap(), 0, attr_list); +cleanup_pipe: + CloseHandle (h_read_pipe); + CloseHandle (h_write_pipe); +cleanup_h_con_reference: + CloseHandle (h_con_reference); +cleanup_h_con_server: + CloseHandle (h_con_server); +cleanup: + return E_FAIL; +} + + extern "C" int sscanf (const char *, const char *, ...); #define close_maybe(h) \ @@ -2136,6 +2266,8 @@ fhandler_pty_master::close (int flag) ssize_t fhandler_pty_master::write (const void *ptr, size_t len) { + size_t towrite = len; + ssize_t ret; char *p = (char *) ptr; termios &ti = tc ()->ti; @@ -2152,7 +2284,7 @@ fhandler_pty_master::write (const void *ptr, size_t len) If the reply for "CSI6n" is divided into multiple writes, pseudo console sometimes does not recognize it. Therefore, put them together into wpbuf and write all at once. */ - static const int wpbuf_len = strlen ("\033[32768;32868R"); + static const int wpbuf_len = 64; /* for response to CSI6n nad CSIc */ static char wpbuf[wpbuf_len]; static int ixput = 0; static int state = 0; @@ -2170,6 +2302,7 @@ fhandler_pty_master::write (const void *ptr, size_t len) } if (state == 1) { + towrite--; if (ixput < wpbuf_len) wpbuf[ixput++] = p[i]; else @@ -2181,21 +2314,27 @@ fhandler_pty_master::write (const void *ptr, size_t len) } } else - line_edit (p + i, 1, ti, &ret); - if (state == 1 && p[i] == 'R') + { + if (ixput) + line_edit (wpbuf, ixput, ti, &ret); + get_ttyp ()->pcon_start = false; + ptr = p + i; + break; + } + if (state == 1 && isalpha(p[i])) state = 2; - } - if (state == 2) - { - /* req_xfer_input is true if "ESC[6n" was sent just for - triggering transfer_input() in master. In this case, - the responce sequence should not be written. */ - if (!get_ttyp ()->req_xfer_input) - WriteFile (to_slave_nat, wpbuf, ixput, &n, NULL); - ixput = 0; - state = 0; - get_ttyp ()->req_xfer_input = false; - get_ttyp ()->pcon_start = false; + if (state == 2) + { + /* req_xfer_input is true if "ESC[6n" was sent just for + triggering transfer_input() in master. In this case, + the responce sequence should not be written. */ + if (!get_ttyp ()->req_xfer_input) + WriteFile (to_slave_nat, wpbuf, ixput, &n, NULL); + if (p[i] == 'R') + get_ttyp ()->req_xfer_input = false; + ixput = 0; + state = 0; + } } ReleaseMutex (input_mutex); @@ -2219,8 +2358,8 @@ fhandler_pty_master::write (const void *ptr, size_t len) } get_ttyp ()->pcon_start_pid = 0; } - - return len; + if (towrite == 0) + return len; } /* Write terminal input to to_slave_nat pipe instead of output_handle @@ -2232,14 +2371,14 @@ fhandler_pty_master::write (const void *ptr, size_t len) is activated. */ tmp_pathbuf tp; char *buf = (char *) ptr; - size_t nlen = len; + size_t nlen = towrite; if (get_ttyp ()->term_code_page != CP_UTF8) { static mbstate_t mbp; buf = tp.c_get (); nlen = NT_MAX_PATH; convert_mb_str (CP_UTF8, buf, &nlen, - get_ttyp ()->term_code_page, (const char *) ptr, len, + get_ttyp ()->term_code_page, (const char *) ptr, nlen, &mbp); } @@ -2255,8 +2394,50 @@ fhandler_pty_master::write (const void *ptr, size_t len) } } + char *bs_pos = (char *) memchr (buf, '\010' /* ^H */, nlen); + HANDLE pcon_owner = NULL; + HANDLE h_pcon_in = NULL; + DWORD resume_pid = 0; + if (bs_pos) + { + pcon_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE, + get_ttyp ()->nat_pipe_owner_pid); + DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_in, + GetCurrentProcess (), &h_pcon_in, + 0, FALSE, DUPLICATE_SAME_ACCESS); + resume_pid = + attach_console_temporarily (get_ttyp()->nat_pipe_owner_pid); + } + DWORD n; - WriteFile (to_slave_nat, buf, nlen, &n, NULL); + while (bs_pos) + { + if (bs_pos - buf > 0) + WriteFile (to_slave_nat, buf, bs_pos - buf, &n, NULL); + INPUT_RECORD r; + r.EventType = KEY_EVENT; + r.Event.KeyEvent.bKeyDown = 1; + r.Event.KeyEvent.wRepeatCount = 0; + r.Event.KeyEvent.wVirtualKeyCode = 0; + r.Event.KeyEvent.wVirtualScanCode = 0; + r.Event.KeyEvent.uChar.AsciiChar = '\010'; /* ^H */ + r.Event.KeyEvent.dwControlKeyState = LEFT_CTRL_PRESSED; + WriteConsoleInput(h_pcon_in, &r, 1, &n); + r.Event.KeyEvent.bKeyDown = 0; + WriteConsoleInput(h_pcon_in, &r, 1, &n); + nlen -= bs_pos - buf + 1; + buf = bs_pos + 1; + bs_pos = (char *) memchr (buf, '\010' /* ^H */, nlen); + } + if (nlen > 0) + WriteFile (to_slave_nat, buf, nlen, &n, NULL); + + if (resume_pid) + resume_from_temporarily_attach (resume_pid); + if (h_pcon_in) + CloseHandle(h_pcon_in); + if (pcon_owner) + CloseHandle(pcon_owner); ReleaseMutex (input_mutex); return len; @@ -2302,11 +2483,30 @@ int fhandler_pty_master::tcgetattr (struct termios *t) { *t = cygwin_shared->tty[get_minor ()]->ti; - /* Workaround for rlwrap v0.40 or later */ - if (get_ttyp ()->pcon_start) - t->c_lflag &= ~(ICANON | ECHO); + if (get_ttyp ()->pcon_activated) - t->c_iflag &= ~ICRNL; + { + t->c_lflag &= ~(ICANON | ECHO); + t->c_iflag &= ~ICRNL; + + HANDLE pcon_owner = OpenProcess (PROCESS_DUP_HANDLE, FALSE, + get_ttyp ()->nat_pipe_owner_pid); + HANDLE h_pcon_in; + DuplicateHandle (pcon_owner, get_ttyp ()->h_pcon_in, + GetCurrentProcess (), &h_pcon_in, + 0, FALSE, DUPLICATE_SAME_ACCESS); + DWORD resume_pid = + attach_console_temporarily (get_ttyp()->nat_pipe_owner_pid); + DWORD mode; + GetConsoleMode (h_pcon_in, &mode); + resume_from_temporarily_attach (resume_pid); + CloseHandle (h_pcon_in); + CloseHandle (pcon_owner); + if (mode & ENABLE_LINE_INPUT) + t->c_lflag |= ICANON; + if (mode & ENABLE_ECHO_INPUT) + t->c_lflag |= ECHO; + } return 0; } @@ -2680,7 +2880,7 @@ fhandler_pty_master::pty_master_fwd_thread (const master_fwd_thread_param_t *p) int state = 0; int start_at = 0; for (DWORD i=0; i<rlen; i++) - if (outbuf[i] == '\033') + if (state == 0 && outbuf[i] == '\033') { start_at = i; state = 1; @@ -2688,12 +2888,14 @@ fhandler_pty_master::pty_master_fwd_thread (const master_fwd_thread_param_t *p) } else if ((state == 1 && outbuf[i] == ']') || (state == 2 && outbuf[i] == '0') || - (state == 3 && outbuf[i] == ';')) + (state == 3 && outbuf[i] == ';') || + (state == 4 && outbuf[i] == '\033')) { state ++; continue; } - else if (state == 4 && outbuf[i] == '\a') + else if ((state == 4 && outbuf[i] == '\a') + || (state == 5 && outbuf[i] == '\\')) { const char *helper_str = "\\bin\\cygwin-console-helper.exe"; if (memmem (&outbuf[start_at], i + 1 - start_at, @@ -3268,9 +3470,14 @@ fhandler_pty_slave::setup_pseudoconsole () const DWORD inherit_cursor = 1; hpcon = NULL; SetLastError (ERROR_SUCCESS); - HRESULT res = CreatePseudoConsole (size, get_handle_nat (), - get_output_handle_nat (), - inherit_cursor, &hpcon); + /* Try OpenConsole.exe before conhost.exe */ + HRESULT res = CreatePseudoConsole_new (size, get_handle_nat (), + get_output_handle_nat (), + inherit_cursor, &hpcon); + if (res != S_OK) /* Fallback to legacy conhost.exe */ + res = CreatePseudoConsole (size, get_handle_nat (), + get_output_handle_nat (), + inherit_cursor, &hpcon); if (res != S_OK || GetLastError () == ERROR_PROC_NOT_FOUND) { if (res != S_OK) @@ -3627,6 +3834,20 @@ fhandler_pty_slave::close_pseudoconsole (tty *ttyp, DWORD force_switch_to) ttyp->nat_pipe_owner_pid = 0; ttyp->pcon_start = false; ttyp->pcon_start_pid = 0; + do + { + pinfo p (ttyp->master_pid); + HANDLE pty_master = OpenProcess (PROCESS_DUP_HANDLE, FALSE, + p->dwProcessId); + HANDLE to_master_nat; + DuplicateHandle (pty_master, ttyp->to_master_nat (), + GetCurrentProcess (), &to_master_nat, + 0, FALSE, DUPLICATE_SAME_ACCESS); + WriteFile (to_master_nat, "\033[?1004l", 8, NULL, NULL); + CloseHandle (to_master_nat); + CloseHandle (pty_master); + } + while (false); } } else -- 2.51.0
libopenconsole.postinstall
(text/plain, 814 B)
if [ $(uname -m) = "x86_64" ]
then
POSTFIX="x64"
else
POSTFIX="x86"
fi
VERSION=$(cat /etc/libopenconsole/version.txt)
cd /tmp
wget -q https://github.com/microsoft/terminal/releases/download/v${VERSION}/Microsoft.WindowsTerminal_${VERSION}_${POSTFIX}.zip -O - > Microsoft.WindowsTerminal_${VERSION}_${POSTFIX}.tmp
if sha256sum --status -c /etc/libopenconsole/Microsoft.WindowsTerminal_${VERSION}_${POSTFIX}.zip.sha256
then
mv Microsoft.WindowsTerminal_${VERSION}_${POSTFIX}.tmp Microsoft.WindowsTerminal_${VERSION}_${POSTFIX}.zip
unzip -jq Microsoft.WindowsTerminal_${VERSION}_${POSTFIX}.zip '*/OpenConsole.exe'
mv OpenConsole.exe /usr/bin/.
else
# Hash mismatch (or failed to download)
rm Microsoft.WindowsTerminal_${VERSION}_${POSTFIX}.tmp
exit 1
fi
chmod a+x /usr/bin/OpenConsole.exe
libopenconsole.preremove
(text/plain, 32 B)
rm -f /usr/bin/OpenConsole.exe
openconsole.cygport
(text/plain, 1.4 KB)
NAME="openconsole"
VERSION=1.23.12811.0
RELEASE=1
CATEGORY="Libs"
SUMMARY="conhost.exe alternative"
DESCRIPTION="conhost.exe alternative for new features of pusedo console"
HOMEPAGE="https://github.com/microsoft/terminal/"
LICENSE="MIT"
ARCH="noarch" # This is noarch because it's just helper shell scrpits.
SRC_URI="${NAME}-${VERSION}.tar.xz"
CYGWIN_FILES="
lib${NAME}.postinstall
lib${NAME}.preremove
"
# Make dummy source file for prep
if [ ! -f ${SRC_URI} ]
then
mkdir -p ${NAME}-${VERSION}
tar acf ${SRC_URI} ${NAME}-${VERSION}
rm -rf ${NAME}-${VERSION}
fi
PKG_NAMES="libopenconsole"
libopenconsole_CONTENTS="etc/"
libopenconsole_REQUIRES="wget unzip"
src_compile() {
:
}
src_install() {
mkdir -p ${D}/etc/lib${NAME}
# Make sha256 hash
wget -q https://github.com/microsoft/terminal/releases/download/v${VERSION}/Microsoft.WindowsTerminal_${VERSION}_x64.zip -O - | sha256sum | sed "s/-$/Microsoft.WindowsTerminal_${VERSION}_x64.tmp/" > ${D}/etc/lib${NAME}/Microsoft.WindowsTerminal_${VERSION}_x64.zip.sha256
wget -q https://github.com/microsoft/terminal/releases/download/v${VERSION}/Microsoft.WindowsTerminal_${VERSION}_x86.zip -O - | sha256sum | sed "s/-$/Microsoft.WindowsTerminal_${VERSION}_x86.tmp/" > ${D}/etc/lib${NAME}/Microsoft.WindowsTerminal_${VERSION}_x86.zip.sha256
# Make version text
echo ${VERSION} > ${D}/etc/lib${NAME}/version.txt
}