Is there a flag or command to make ConPTY not echo/output the event of screen buffer size changed? #18372

Closed
opened 2026-01-31 06:11:49 +00:00 by claunia · 6 comments
Owner

Originally created by @Tancen on GitHub (Sep 3, 2022).

I am coding two programs, using ConPTY as a server-side run on windows, and a client runs on Linux. when the client console window size changed, the client will send '\e[8;w;ht'(w is the window real width, h is the window real height) to the server. The server received the message and writes it into the pty, but the message '\e[8;w;ht....... ' will be output back from pty and sent to the client. the client received the message and print it, and resize the window for the VT100 command, a new 'screen buffer size changed' event occurs from the client, ...... infinite loop.

Originally created by @Tancen on GitHub (Sep 3, 2022). I am coding two programs, using ConPTY as a server-side run on windows, and a client runs on Linux. when the client console window size changed, the client will send '\e[8;w;ht'(w is the window real width, h is the window real height) to the server. The server received the message and writes it into the pty, but the message '\e[8;w;ht....... ' will be output back from pty and sent to the client. the client received the message and print it, and resize the window for the VT100 command, a new 'screen buffer size changed' event occurs from the client, ...... infinite loop.
claunia added the Issue-FeatureNeeds-TriageNeeds-Tag-FixNeeds-Attention labels 2026-01-31 06:11:50 +00:00
Author
Owner

@zadjii-msft commented on GitHub (Sep 7, 2022):

We might need more details on the scenario here. A more concrete repro case.

I don't think that conpty should be re-emitting a resize for the size it was just resized to. What OS version is ConPTY running on/? There's all sorts of code around VtEngine::SuppressResizeRepaint that should be preventing this.

@zadjii-msft commented on GitHub (Sep 7, 2022): We might need more details on the scenario here. A more concrete repro case. I don't think that conpty should be re-emitting a resize for the size it was just resized to. What OS version is ConPTY running on/? There's all sorts of code around `VtEngine::SuppressResizeRepaint` that should be preventing this.
Author
Owner

@Tancen commented on GitHub (Sep 8, 2022):

This is the test code, it's can repro the case:

#include <Windows.h>
#include <stdio.h>
#include <assert.h>
#include <string>
#include <memory>
#include <time.h>

int main(int argc, char* argv[])
{

    HPCON hPC = 0;
    PROCESS_INFORMATION pi = { 0 };
    HANDLE outPipeOurSide = nullptr, inPipeOurSide = nullptr;
    HANDLE outPipePseudoConsoleSide = nullptr, inPipePseudoConsoleSide = nullptr;
    int err;
    bool success;

    // create pty
    CreatePipe(&inPipePseudoConsoleSide, &inPipeOurSide, NULL, 0);
    CreatePipe(&outPipeOurSide, &outPipePseudoConsoleSide, NULL, 0);

    CONSOLE_SCREEN_BUFFER_INFOEX info = { 0 };
    info.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
    success = GetConsoleScreenBufferInfoEx(GetStdHandle(STD_OUTPUT_HANDLE), &info);
    assert(success);

    short w = info.srWindow.Right - info.srWindow.Left + 1;
    short h = info.srWindow.Bottom - info.srWindow.Top + 1;

    err = CreatePseudoConsole(COORD{ w, h }, inPipePseudoConsoleSide, outPipePseudoConsoleSide, 0, &hPC);
    assert(err == S_OK);

    STARTUPINFOEXW  si;
    memset(&si, 0, sizeof(si));
    si.StartupInfo.cb = sizeof(STARTUPINFOEXW);

    size_t size;
    InitializeProcThreadAttributeList(NULL, 1, 0, &size);
    si.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(new BYTE[size]);
    success = InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, (PSIZE_T)&size);
    assert(success);

    success = UpdateProcThreadAttribute(
        si.lpAttributeList,
        0,
        PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
        hPC,
        sizeof(hPC),
        NULL,
        NULL);
    assert(success);

    std::wstring commandline = L"cmd.exe";
    std::unique_ptr<wchar_t> bufCommandline(new wchar_t[commandline.length() + 1]);
    memcpy(bufCommandline.get(), commandline.c_str(), (commandline.length() + 1) * sizeof(wchar_t));
    success = CreateProcessW(
        nullptr,
        bufCommandline.get(),
        nullptr,
        nullptr,
        TRUE,
        EXTENDED_STARTUPINFO_PRESENT,
        nullptr,
        nullptr,
        &si.StartupInfo,
        &pi);

    assert(success);
    DeleteProcThreadAttributeList(si.lpAttributeList);


    DWORD  settings;
    HANDLE hConsole;

    //change code page
    system("chcp  65001 > nul");    // utf-8

    //print output
    hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    assert(hConsole != INVALID_HANDLE_VALUE);
    success = GetConsoleMode(hConsole, &settings);
    assert(success);
    settings |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    success = SetConsoleMode(hConsole, settings);
    assert(success);

    const int BUF_LEN = 4096;
    std::unique_ptr<char> buf(new char[BUF_LEN]);
    time_t t = time(NULL);
    bool writtenResize = false;
    do
    {
        DWORD n = 0;
        success = PeekNamedPipe(outPipeOurSide, nullptr, 0, nullptr, &n, nullptr);
        assert(success);

        if (n)
        {
            if (n > BUF_LEN)
                n = BUF_LEN;

            DWORD l;
            success = ReadFile(outPipeOurSide, buf.get(), n, &l, nullptr);
            assert(success);

            for (int i = 0; i < l; i++)
                printf("%c", buf.get()[i]);

            FILE* pf = nullptr;
            {
                errno_t err = fopen_s(&pf, "e:/out.txt", "ab");
                assert(pf);
            }
            int ret = fwrite(buf.get(), l, 1, pf);
            assert(ret == 1);
            fclose(pf);
        }

        if (time(NULL) - t > 5 && !writtenResize) // after 5 seconds, write resize command
        {
            //type command
            short width = 600, height = 400;
            std::string command;
            command.append(1, 0x1b).append("[8;").append(std::to_string(width)).append(";").append(std::to_string(height)).append(1, 't');
            DWORD l;
            success = WriteFile(inPipeOurSide, command.c_str(), command.length(), &l, nullptr);
            assert(success && l == command.length());

            writtenResize = true;
        }
    } while (true);


    CloseHandle(pi.hProcess);

    CloseHandle(outPipeOurSide);
    CloseHandle(inPipeOurSide);
    CloseHandle(outPipePseudoConsoleSide);
    CloseHandle(inPipePseudoConsoleSide);

    ClosePseudoConsole(hPC);
    return 0;
}

The output file : out.txt

@Tancen commented on GitHub (Sep 8, 2022): This is the test code, it's can repro the case: ``` #include <Windows.h> #include <stdio.h> #include <assert.h> #include <string> #include <memory> #include <time.h> int main(int argc, char* argv[]) { HPCON hPC = 0; PROCESS_INFORMATION pi = { 0 }; HANDLE outPipeOurSide = nullptr, inPipeOurSide = nullptr; HANDLE outPipePseudoConsoleSide = nullptr, inPipePseudoConsoleSide = nullptr; int err; bool success; // create pty CreatePipe(&inPipePseudoConsoleSide, &inPipeOurSide, NULL, 0); CreatePipe(&outPipeOurSide, &outPipePseudoConsoleSide, NULL, 0); CONSOLE_SCREEN_BUFFER_INFOEX info = { 0 }; info.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); success = GetConsoleScreenBufferInfoEx(GetStdHandle(STD_OUTPUT_HANDLE), &info); assert(success); short w = info.srWindow.Right - info.srWindow.Left + 1; short h = info.srWindow.Bottom - info.srWindow.Top + 1; err = CreatePseudoConsole(COORD{ w, h }, inPipePseudoConsoleSide, outPipePseudoConsoleSide, 0, &hPC); assert(err == S_OK); STARTUPINFOEXW si; memset(&si, 0, sizeof(si)); si.StartupInfo.cb = sizeof(STARTUPINFOEXW); size_t size; InitializeProcThreadAttributeList(NULL, 1, 0, &size); si.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(new BYTE[size]); success = InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, (PSIZE_T)&size); assert(success); success = UpdateProcThreadAttribute( si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, hPC, sizeof(hPC), NULL, NULL); assert(success); std::wstring commandline = L"cmd.exe"; std::unique_ptr<wchar_t> bufCommandline(new wchar_t[commandline.length() + 1]); memcpy(bufCommandline.get(), commandline.c_str(), (commandline.length() + 1) * sizeof(wchar_t)); success = CreateProcessW( nullptr, bufCommandline.get(), nullptr, nullptr, TRUE, EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, &si.StartupInfo, &pi); assert(success); DeleteProcThreadAttributeList(si.lpAttributeList); DWORD settings; HANDLE hConsole; //change code page system("chcp 65001 > nul"); // utf-8 //print output hConsole = GetStdHandle(STD_OUTPUT_HANDLE); assert(hConsole != INVALID_HANDLE_VALUE); success = GetConsoleMode(hConsole, &settings); assert(success); settings |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; success = SetConsoleMode(hConsole, settings); assert(success); const int BUF_LEN = 4096; std::unique_ptr<char> buf(new char[BUF_LEN]); time_t t = time(NULL); bool writtenResize = false; do { DWORD n = 0; success = PeekNamedPipe(outPipeOurSide, nullptr, 0, nullptr, &n, nullptr); assert(success); if (n) { if (n > BUF_LEN) n = BUF_LEN; DWORD l; success = ReadFile(outPipeOurSide, buf.get(), n, &l, nullptr); assert(success); for (int i = 0; i < l; i++) printf("%c", buf.get()[i]); FILE* pf = nullptr; { errno_t err = fopen_s(&pf, "e:/out.txt", "ab"); assert(pf); } int ret = fwrite(buf.get(), l, 1, pf); assert(ret == 1); fclose(pf); } if (time(NULL) - t > 5 && !writtenResize) // after 5 seconds, write resize command { //type command short width = 600, height = 400; std::string command; command.append(1, 0x1b).append("[8;").append(std::to_string(width)).append(";").append(std::to_string(height)).append(1, 't'); DWORD l; success = WriteFile(inPipeOurSide, command.c_str(), command.length(), &l, nullptr); assert(success && l == command.length()); writtenResize = true; } } while (true); CloseHandle(pi.hProcess); CloseHandle(outPipeOurSide); CloseHandle(inPipeOurSide); CloseHandle(outPipePseudoConsoleSide); CloseHandle(inPipePseudoConsoleSide); ClosePseudoConsole(hPC); return 0; } ``` The output file : [out.txt](https://github.com/Tancen/tmp/blob/de213b49493528c8c52e6182bbdd3f8d25dd3621/out.txt)
Author
Owner

@DHowett commented on GitHub (Sep 8, 2022):

CSI t is not intended to be sent to a client application that is not prepared for it. A window resize message is supposed to be generated by a client and sent to a terminal emulator. However, on line 126 you are telling ConPTY to send CSI t to the client application.

You should only write things into inPipeOurSide that you want the client application to receive.

If you want to resize the console host (which will trigger the correct kind of window resize message sent to the client), you should use ResizePseudoConsole.

@DHowett commented on GitHub (Sep 8, 2022): `CSI t` is not intended to be sent to a client application that is not prepared for it. A window resize message is supposed to be generated _by_ a client and sent _to_ a terminal emulator. However, on line 126 you are telling ConPTY to send `CSI t` to the client application. You should only write things into `inPipeOurSide` that you want the client application to receive. If you want to _resize the console host_ (which will trigger the _correct_ kind of window resize message sent to the client), you should use `ResizePseudoConsole`.
Author
Owner

@DHowett commented on GitHub (Sep 8, 2022):

Other bugs:

On line 76 you are using system to call chcp. This runs cmd.exe /s /c chcp 65001. You should just call SetConsoleOutputCP instead. It has much less overhead. When you use system, you force the creation of another process.

@DHowett commented on GitHub (Sep 8, 2022): Other bugs: On line 76 you are using `system` to call `chcp`. This runs `cmd.exe /s /c chcp 65001`. You should just call `SetConsoleOutputCP` instead. It has much less overhead. When you use `system`, you force the creation of another process.
Author
Owner

@ghost commented on GitHub (Sep 12, 2022):

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment.

@ghost commented on GitHub (Sep 12, 2022): This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for **4 days**. It will be closed if no further activity occurs **within 3 days of this comment**.
Author
Owner

@Tancen commented on GitHub (Sep 13, 2022):

I changed the test codes as you said, and ran it on my one machine( Windows 11).

#include <Windows.h>
#include <stdio.h>
#include <assert.h>
#include <string>
#include <memory>
#include <time.h>

int main(int argc, char* argv[])
{

    HPCON hPC = 0;
    PROCESS_INFORMATION pi = { 0 };
    HANDLE outPipeOurSide = nullptr, inPipeOurSide = nullptr;
    HANDLE outPipePseudoConsoleSide = nullptr, inPipePseudoConsoleSide = nullptr;
    int err;
    bool success;

    // create pty
    CreatePipe(&inPipePseudoConsoleSide, &inPipeOurSide, NULL, 0);
    CreatePipe(&outPipeOurSide, &outPipePseudoConsoleSide, NULL, 0);

    CONSOLE_SCREEN_BUFFER_INFOEX info = { 0 };
    info.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
    success = GetConsoleScreenBufferInfoEx(GetStdHandle(STD_OUTPUT_HANDLE), &info);
    assert(success);

    short w = info.srWindow.Right - info.srWindow.Left + 1;
    short h = info.srWindow.Bottom - info.srWindow.Top + 1;

    err = CreatePseudoConsole(COORD{ w, h }, inPipePseudoConsoleSide, outPipePseudoConsoleSide, 0, &hPC);
    assert(err == S_OK);

    STARTUPINFOEXW  si;
    memset(&si, 0, sizeof(si));
    si.StartupInfo.cb = sizeof(STARTUPINFOEXW);

    size_t size;
    InitializeProcThreadAttributeList(NULL, 1, 0, &size);
    si.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(new BYTE[size]);
    success = InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, (PSIZE_T)&size);
    assert(success);

    success = UpdateProcThreadAttribute(
        si.lpAttributeList,
        0,
        PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
        hPC,
        sizeof(hPC),
        NULL,
        NULL);
    assert(success);

    std::wstring commandline = L"cmd.exe";
    std::unique_ptr<wchar_t> bufCommandline(new wchar_t[commandline.length() + 1]);
    memcpy(bufCommandline.get(), commandline.c_str(), (commandline.length() + 1) * sizeof(wchar_t));
    success = CreateProcessW(
        nullptr,
        bufCommandline.get(),
        nullptr,
        nullptr,
        TRUE,
        EXTENDED_STARTUPINFO_PRESENT,
        nullptr,
        nullptr,
        &si.StartupInfo,
        &pi);

    assert(success);
    DeleteProcThreadAttributeList(si.lpAttributeList);


    DWORD  settings;
    HANDLE hConsole;


    //print output
    success = SetConsoleOutputCP(65001); //change code page
    assert(success);

    hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    assert(hConsole != INVALID_HANDLE_VALUE);

    success = GetConsoleMode(hConsole, &settings);
    assert(success);
    settings |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    success = SetConsoleMode(hConsole, settings);
    assert(success);

    const int BUF_LEN = 4096;
    std::unique_ptr<char> buf(new char[BUF_LEN]);
    time_t t = time(NULL);
    bool writtenResize = false;
    do
    {
        DWORD n = 0;
        success = PeekNamedPipe(outPipeOurSide, nullptr, 0, nullptr, &n, nullptr);
        assert(success);

        if (n)
        {
            if (n > BUF_LEN)
                n = BUF_LEN;

            DWORD l;
            success = ReadFile(outPipeOurSide, buf.get(), n, &l, nullptr);
            assert(success);

            for (int i = 0; i < l; i++)
                printf("%c", buf.get()[i]);

            FILE* pf = nullptr;
            {
                errno_t err = fopen_s(&pf, "e:/out2.txt", "ab");
                assert(pf);
            }
            int ret = fwrite(buf.get(), l, 1, pf);
            assert(ret == 1);
            fclose(pf);
        }

        if (time(NULL) - t > 5 && !writtenResize) // after 5 seconds, write resize command
        {
            //type command
            short width = 600, height = 400;
            /*std::string command;
            command.append(1, 0x1b).append("[8;").append(std::to_string(width)).append(";").append(std::to_string(height)).append(1, 't');
            DWORD l;
            success = WriteFile(inPipeOurSide, command.c_str(), command.length(), &l, nullptr);
            assert(success && l == command.length());*/
            ResizePseudoConsole(hPC, { width, height });
            writtenResize = true;
        }
    } while (true);


    CloseHandle(pi.hProcess);

    CloseHandle(outPipeOurSide);
    CloseHandle(inPipeOurSide);
    CloseHandle(outPipePseudoConsoleSide);
    CloseHandle(inPipePseudoConsoleSide);

    ClosePseudoConsole(hPC);
    return 0;
}

It still echoes the size change event. out2.txt
But, on my other machine(Windows 10), it does not echo.

CSI t is not intended to be sent to a client application that is not prepared for it. A window resize message is supposed to be generated by a client and sent to a terminal emulator. However, on line 126 you are telling ConPTY to send CSI t to the client application.

You should only write things into inPipeOurSide that you want the client application to receive.

If you want to resize the console host (which will trigger the correct kind of window resize message sent to the client), you should use ResizePseudoConsole.

Line 126 (at new test, it's line 130) just simulates the client window doing resize, and the ‘conpty’ synchronize it.

@Tancen commented on GitHub (Sep 13, 2022): I changed the test codes as you said, and ran it on my one machine( Windows 11). ```c++ #include <Windows.h> #include <stdio.h> #include <assert.h> #include <string> #include <memory> #include <time.h> int main(int argc, char* argv[]) { HPCON hPC = 0; PROCESS_INFORMATION pi = { 0 }; HANDLE outPipeOurSide = nullptr, inPipeOurSide = nullptr; HANDLE outPipePseudoConsoleSide = nullptr, inPipePseudoConsoleSide = nullptr; int err; bool success; // create pty CreatePipe(&inPipePseudoConsoleSide, &inPipeOurSide, NULL, 0); CreatePipe(&outPipeOurSide, &outPipePseudoConsoleSide, NULL, 0); CONSOLE_SCREEN_BUFFER_INFOEX info = { 0 }; info.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX); success = GetConsoleScreenBufferInfoEx(GetStdHandle(STD_OUTPUT_HANDLE), &info); assert(success); short w = info.srWindow.Right - info.srWindow.Left + 1; short h = info.srWindow.Bottom - info.srWindow.Top + 1; err = CreatePseudoConsole(COORD{ w, h }, inPipePseudoConsoleSide, outPipePseudoConsoleSide, 0, &hPC); assert(err == S_OK); STARTUPINFOEXW si; memset(&si, 0, sizeof(si)); si.StartupInfo.cb = sizeof(STARTUPINFOEXW); size_t size; InitializeProcThreadAttributeList(NULL, 1, 0, &size); si.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(new BYTE[size]); success = InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, (PSIZE_T)&size); assert(success); success = UpdateProcThreadAttribute( si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, hPC, sizeof(hPC), NULL, NULL); assert(success); std::wstring commandline = L"cmd.exe"; std::unique_ptr<wchar_t> bufCommandline(new wchar_t[commandline.length() + 1]); memcpy(bufCommandline.get(), commandline.c_str(), (commandline.length() + 1) * sizeof(wchar_t)); success = CreateProcessW( nullptr, bufCommandline.get(), nullptr, nullptr, TRUE, EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, &si.StartupInfo, &pi); assert(success); DeleteProcThreadAttributeList(si.lpAttributeList); DWORD settings; HANDLE hConsole; //print output success = SetConsoleOutputCP(65001); //change code page assert(success); hConsole = GetStdHandle(STD_OUTPUT_HANDLE); assert(hConsole != INVALID_HANDLE_VALUE); success = GetConsoleMode(hConsole, &settings); assert(success); settings |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; success = SetConsoleMode(hConsole, settings); assert(success); const int BUF_LEN = 4096; std::unique_ptr<char> buf(new char[BUF_LEN]); time_t t = time(NULL); bool writtenResize = false; do { DWORD n = 0; success = PeekNamedPipe(outPipeOurSide, nullptr, 0, nullptr, &n, nullptr); assert(success); if (n) { if (n > BUF_LEN) n = BUF_LEN; DWORD l; success = ReadFile(outPipeOurSide, buf.get(), n, &l, nullptr); assert(success); for (int i = 0; i < l; i++) printf("%c", buf.get()[i]); FILE* pf = nullptr; { errno_t err = fopen_s(&pf, "e:/out2.txt", "ab"); assert(pf); } int ret = fwrite(buf.get(), l, 1, pf); assert(ret == 1); fclose(pf); } if (time(NULL) - t > 5 && !writtenResize) // after 5 seconds, write resize command { //type command short width = 600, height = 400; /*std::string command; command.append(1, 0x1b).append("[8;").append(std::to_string(width)).append(";").append(std::to_string(height)).append(1, 't'); DWORD l; success = WriteFile(inPipeOurSide, command.c_str(), command.length(), &l, nullptr); assert(success && l == command.length());*/ ResizePseudoConsole(hPC, { width, height }); writtenResize = true; } } while (true); CloseHandle(pi.hProcess); CloseHandle(outPipeOurSide); CloseHandle(inPipeOurSide); CloseHandle(outPipePseudoConsoleSide); CloseHandle(inPipePseudoConsoleSide); ClosePseudoConsole(hPC); return 0; } ``` It still echoes the size change event. [out2.txt](https://github.com/Tancen/tmp/blob/main/out2.txt) But, on my other machine(Windows 10), it does not echo. > `CSI t` is not intended to be sent to a client application that is not prepared for it. A window resize message is supposed to be generated _by_ a client and sent _to_ a terminal emulator. However, on line 126 you are telling ConPTY to send `CSI t` to the client application. > > You should only write things into `inPipeOurSide` that you want the client application to receive. > > If you want to _resize the console host_ (which will trigger the _correct_ kind of window resize message sent to the client), you should use `ResizePseudoConsole`. Line 126 (at new test, it's line 130) just simulates the client window doing resize, and the ‘conpty’ synchronize it.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#18372