[Guidance] getting VT sequences without ENABLE_VIRTUAL_TERMINAL_PROCESSING enabled #2842

Open
opened 2026-01-30 23:06:50 +00:00 by claunia · 0 comments
Owner

Originally created by @therealkenc on GitHub (Jul 19, 2019).

Originally assigned to: @bitcrazed on GitHub.

This is part one of a harder question, but I need to get over a basic hump first. I need to give a child process a pty as it's standard output, even though the parent may not have a tty (in Windows parlance, a console) itself. You can assume the parent has no stdin/stdout/stderr. Maybe it's a network daemon.

What I am finding is that even if I write something simple ("hello from child") to the child's stdout handle, in the parent, when I read off the pipe end, I get:

"\x1b[25l\x1b[2J\x1b[m\x1b[Hhello from child\r\n\x1b]0;C:\\Users\\there\\source\\repos\\samples\\ConPTY\\EchoCon\\x64\\Debug\\Ech

The parent doesn't know how to process any of that, because the parent isn't necessarily a terminal emulator. And the child might not even be a console application for all we know, it could be sending house-cat GIFs to stdout. Not our business.

The guidance sought is: how do I coerce the code below such that the parent gets exactly the text "hello from child" from ReadFile(pipe_in). Maybe something silly, but I've run out of ideas except to ask.

This question is in furtherance to finding some elegant solution WSL#3279.

#include "stdafx.h"
#include <Windows.h>
#include <process.h>

void parent(void)
{
  // TL;DR create process with ConPTY pipes, the read from child is further below
  HANDLE pty_in, pty_out, pipe_in, pipe_out;
  CreatePipe(&pty_in, &pipe_out, NULL, 0);
  CreatePipe(&pipe_in, &pty_out, NULL, 0);
  COORD con_sz = { 80, 24 };
  HPCON pty;
  CreatePseudoConsole(con_sz, pty_in, pty_out, 0, &pty);
  CloseHandle(pty_out);
  CloseHandle(pty_in);
  STARTUPINFOEXW startup_info;
  memset(&startup_info, 0, sizeof(startup_info));
  SIZE_T attr_sz = 0;
  startup_info.StartupInfo.cb = sizeof(STARTUPINFOEXW);
  InitializeProcThreadAttributeList(NULL, 1, 0, &attr_sz);
  startup_info.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(attr_sz);
  InitializeProcThreadAttributeList(startup_info.lpAttributeList, 1, 0, &attr_sz);
  UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, 
    PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, pty, sizeof(HPCON), NULL, NULL);
  PROCESS_INFORMATION client_info;
  memset(&client_info, 0, sizeof(PROCESS_INFORMATION));
  // self, happened to begin life as EchoCon.exe
  wchar_t cmd[] = L"EchoCon.exe child";
  CreateProcessW(NULL, cmd, NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT,
    NULL, NULL, &startup_info.StartupInfo, &client_info);
  
  // proof of life hello from parent output
  HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
  char buf[256];
  snprintf(buf, sizeof(buf), "hello from parent\n");
  DWORD towrite = (DWORD)strlen(buf);
  DWORD nwritten;
  WriteFile(out, buf, towrite, &nwritten, NULL);
  Sleep(1000);

  // finally the read part
  DWORD nread;
  memset(buf, 0, sizeof(buf));
  // gets: "\x1b[25l\x1b[2J\x1b[m\x1b[Hhello from child\r\n\x1b]0;C:\\Users\\there\\source\\repos\\samples\\ConPTY\\EchoCon\\x64\\Debug\\EchoCon.exe\a\x1b[?25h"
  ReadFile(pipe_in, buf, (DWORD)sizeof(buf), &nread, NULL);
  snprintf(buf, sizeof(buf), "got num bytes: %d\n", nread);
  towrite = (DWORD)strlen(buf);
  WriteFile(out, buf, towrite, &nwritten, NULL);
  Sleep(500);
}

void child()
{
  HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
  char buf[256];
  snprintf(buf, sizeof(buf), "hello from child\n");
  DWORD towrite = (DWORD)strlen(buf);
  DWORD nwritten;
  WriteFile(out, buf, towrite, &nwritten, NULL);
}

int main(int argc, const char* argv[])
{
  HANDLE stdout_con = GetStdHandle(STD_OUTPUT_HANDLE);
  DWORD console_mode;
  GetConsoleMode(stdout_con, &console_mode);
  // for good measure
  console_mode &= ~ENABLE_VIRTUAL_TERMINAL_PROCESSING;
  SetConsoleMode(stdout_con, console_mode);
  (argc == 2 && strcmp(argv[1], "child") == 0) ? child() : parent();
  return 0;
}

[n.b. In the example, the parent does have a console, but just for caveman debugging purposes. If you prefer, imagine the parent's WriteFile(out... is debugging out a socket. Or that there are no writes at all in the parent, and I'm setting breakpoints.]

Originally created by @therealkenc on GitHub (Jul 19, 2019). Originally assigned to: @bitcrazed on GitHub. This is part one of a harder question, but I need to get over a basic hump first. I need to give a child process a pty as it's standard output, even though the parent may not have a tty (in Windows parlance, a console) itself. You can assume the parent has no `stdin`/`stdout`/`stderr`. Maybe it's a network daemon. What I am finding is that even if I write something simple (`"hello from child"`) to the child's `stdout` handle, in the parent, when I read off the pipe end, I get: ``` "\x1b[25l\x1b[2J\x1b[m\x1b[Hhello from child\r\n\x1b]0;C:\\Users\\there\\source\\repos\\samples\\ConPTY\\EchoCon\\x64\\Debug\\Ech ``` The parent doesn't know how to process any of that, because the parent isn't necessarily a terminal emulator. And the child might not even be a console application for all we know, it could be sending house-cat GIFs to `stdout`. Not our business. **The guidance sought is**: how do I coerce the code below such that the parent gets exactly the text `"hello from child"` from `ReadFile(pipe_in)`. Maybe something silly, but I've run out of ideas except to ask. This question is in furtherance to finding some elegant solution [WSL#3279](https://github.com/microsoft/WSL/issues/3279). ```C #include "stdafx.h" #include <Windows.h> #include <process.h> void parent(void) { // TL;DR create process with ConPTY pipes, the read from child is further below HANDLE pty_in, pty_out, pipe_in, pipe_out; CreatePipe(&pty_in, &pipe_out, NULL, 0); CreatePipe(&pipe_in, &pty_out, NULL, 0); COORD con_sz = { 80, 24 }; HPCON pty; CreatePseudoConsole(con_sz, pty_in, pty_out, 0, &pty); CloseHandle(pty_out); CloseHandle(pty_in); STARTUPINFOEXW startup_info; memset(&startup_info, 0, sizeof(startup_info)); SIZE_T attr_sz = 0; startup_info.StartupInfo.cb = sizeof(STARTUPINFOEXW); InitializeProcThreadAttributeList(NULL, 1, 0, &attr_sz); startup_info.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)malloc(attr_sz); InitializeProcThreadAttributeList(startup_info.lpAttributeList, 1, 0, &attr_sz); UpdateProcThreadAttribute(startup_info.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, pty, sizeof(HPCON), NULL, NULL); PROCESS_INFORMATION client_info; memset(&client_info, 0, sizeof(PROCESS_INFORMATION)); // self, happened to begin life as EchoCon.exe wchar_t cmd[] = L"EchoCon.exe child"; CreateProcessW(NULL, cmd, NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &startup_info.StartupInfo, &client_info); // proof of life hello from parent output HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); char buf[256]; snprintf(buf, sizeof(buf), "hello from parent\n"); DWORD towrite = (DWORD)strlen(buf); DWORD nwritten; WriteFile(out, buf, towrite, &nwritten, NULL); Sleep(1000); // finally the read part DWORD nread; memset(buf, 0, sizeof(buf)); // gets: "\x1b[25l\x1b[2J\x1b[m\x1b[Hhello from child\r\n\x1b]0;C:\\Users\\there\\source\\repos\\samples\\ConPTY\\EchoCon\\x64\\Debug\\EchoCon.exe\a\x1b[?25h" ReadFile(pipe_in, buf, (DWORD)sizeof(buf), &nread, NULL); snprintf(buf, sizeof(buf), "got num bytes: %d\n", nread); towrite = (DWORD)strlen(buf); WriteFile(out, buf, towrite, &nwritten, NULL); Sleep(500); } void child() { HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); char buf[256]; snprintf(buf, sizeof(buf), "hello from child\n"); DWORD towrite = (DWORD)strlen(buf); DWORD nwritten; WriteFile(out, buf, towrite, &nwritten, NULL); } int main(int argc, const char* argv[]) { HANDLE stdout_con = GetStdHandle(STD_OUTPUT_HANDLE); DWORD console_mode; GetConsoleMode(stdout_con, &console_mode); // for good measure console_mode &= ~ENABLE_VIRTUAL_TERMINAL_PROCESSING; SetConsoleMode(stdout_con, console_mode); (argc == 2 && strcmp(argv[1], "child") == 0) ? child() : parent(); return 0; } ``` [n.b. In the example, the parent _does_ have a console, but just for caveman debugging purposes. If you prefer, imagine the parent's `WriteFile(out...` is debugging out a socket. Or that there are no writes at all in the parent, and I'm setting breakpoints.]
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#2842