terminal icon indicating copy to clipboard operation
terminal copied to clipboard

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

Open Tancen opened this issue 2 years ago • 6 comments

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.

Tancen avatar Sep 03 '22 09:09 Tancen

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 avatar Sep 07 '22 11:09 zadjii-msft

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 avatar Sep 08 '22 13:09 Tancen

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 avatar Sep 08 '22 18:09 DHowett

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 avatar Sep 08 '22 18:09 DHowett

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 avatar Sep 12 '22 19:09 ghost

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 avatar Sep 13 '22 12:09 Tancen