ConPTY could optimize some newline movement #17822

Closed
opened 2026-01-31 05:55:22 +00:00 by claunia · 6 comments
Owner

Originally created by @Tancen on GitHub (Jun 29, 2022).

Windows Terminal version

1.13.11432.0

Windows build number

10.0.22000.739

Other Software

No response

Steps to reproduce

My program(test5.exe) loads the file 'tmp2.txt' and prints each character, At the end of the file, The terminal processing characters '\1b[34;1H' incorrect, It doesn't jump to line 34, Line 33 jumped to. And some characters of line 33 were overwritten.

image

image

Codes of the test:

#include <Windows.h>
#include <stdio.h>
#include <assert.h>

int main(int argc, char* argv[])
{
    DWORD  settings;
    HANDLE hConsole;
    bool success;

    //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);

    FILE* pf;
    int err = fopen_s(&pf, "e:/tmp2.txt", "rb");
    assert(!err);

    do
    {
        unsigned ch;
        int ret = fread(&ch, 1, 1, pf);
        if (ret <= 0)
            break;
        printf("%c", ch);
    } while (true);
    fclose(pf);
    return 0;
}

Expected Behavior

Output characters to line 34.

Actual Behavior

Output characters to line 33.

Originally created by @Tancen on GitHub (Jun 29, 2022). ### Windows Terminal version 1.13.11432.0 ### Windows build number 10.0.22000.739 ### Other Software _No response_ ### Steps to reproduce My program(test5.exe) loads the file 'tmp2.txt' and prints each character, At the end of the file, The terminal processing characters '\1b[34;1H' incorrect, It doesn't jump to line 34, Line 33 jumped to. And some characters of line 33 were overwritten. ![image](https://user-images.githubusercontent.com/3649637/176457686-d2fb7bd0-c6c3-4b32-b70d-fced3a717f6d.png) ![image](https://user-images.githubusercontent.com/3649637/176457869-d5514a12-fe8f-4d54-b4a2-0a1894ce9c8e.png) Codes of the test: ``` #include <Windows.h> #include <stdio.h> #include <assert.h> int main(int argc, char* argv[]) { DWORD settings; HANDLE hConsole; bool success; //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); FILE* pf; int err = fopen_s(&pf, "e:/tmp2.txt", "rb"); assert(!err); do { unsigned ch; int ret = fread(&ch, 1, 1, pf); if (ret <= 0) break; printf("%c", ch); } while (true); fclose(pf); return 0; } ``` ### Expected Behavior Output characters to line 34. ### Actual Behavior Output characters to line 33.
claunia added the Area-RenderingIssue-BugNeeds-Tag-FixProduct-ConptyPriority-2 labels 2026-01-31 05:55:23 +00:00
Author
Owner

@WSLUser commented on GitHub (Jun 29, 2022):

So there's https://terminalnuget.blob.core.windows.net/packages/TerminalSequences.html, which lists what WT supports. However it has been awhile since that document was generated and number of changes have been made since then. It would be nice if a new document was generated to list the current support of WT. For example, none of the OSC sequences are even listed such as hyperlink support. WT also now supports DRCS but that isn't listed on the document due to it's age. Adding the ConEmu custom sequences currently supported to that list would also be helpful.

@WSLUser commented on GitHub (Jun 29, 2022): So there's https://terminalnuget.blob.core.windows.net/packages/TerminalSequences.html, which lists what WT supports. However it has been awhile since that document was generated and number of changes have been made since then. It would be nice if a new document was generated to list the current support of WT. For example, none of the OSC sequences are even listed such as hyperlink support. WT also now supports DRCS but that isn't listed on the document due to it's age. Adding the ConEmu custom sequences currently supported to that list would also be helpful.
Author
Owner

@DHowett commented on GitHub (Jun 29, 2022):

Can you share the test file as well? I have a couple questions that it could answer 😄

@DHowett commented on GitHub (Jun 29, 2022): Can you share the test file as well? I have a couple questions that it could answer 😄
Author
Owner

@Tancen commented on GitHub (Jun 30, 2022):

Can you share the test file as well? I have a couple questions that it could answer 😄

tmp2.txt

@Tancen commented on GitHub (Jun 30, 2022): > Can you share the test file as well? I have a couple questions that it could answer 😄 [tmp2.txt](https://github.com/Tancen/tmp/blob/8bb54befcb7ec8671f2d1946e4c28365afb4933b/tmp2.txt)
Author
Owner

@j4james commented on GitHub (Jul 2, 2022):

I'm assuming your code page is 936, but it would help if you could confirm that, because that might be a factor (just type chcp at the cmd prompt to see the active code page).

But I suspect the problem is that your screen height was set to 32 lines or less when you ran the test, because that's the only way I can reproduce the output you're seeing.

The \1b[34;1H escape sequence is trying to move to line 34, but if there aren't 34 lines on the screen it's just going to move down as far as it can go. And at that point in time, the last line is 5 涓洰褰?67,369,209,856 鍙敤瀛楄妭, so that's why that line is overwritten.

@j4james commented on GitHub (Jul 2, 2022): I'm assuming your code page is 936, but it would help if you could confirm that, because that might be a factor (just type `chcp` at the cmd prompt to see the active code page). But I suspect the problem is that your screen height was set to 32 lines or less when you ran the test, because that's the only way I can reproduce the output you're seeing. The `\1b[34;1H` escape sequence is trying to move to line 34, but if there aren't 34 lines on the screen it's just going to move down as far as it can go. And at that point in time, the last line is `5 涓洰褰?67,369,209,856 鍙敤瀛楄妭`, so that's why that line is overwritten.
Author
Owner

@Tancen commented on GitHub (Jul 4, 2022):

I'm assuming your code page is 936, but it would help if you could confirm that, because that might be a factor (just type chcp at the cmd prompt to see the active code page).

But I suspect the problem is that your screen height was set to 32 lines or less when you ran the test, because that's the only way I can reproduce the output you're seeing.

The \1b[34;1H escape sequence is trying to move to line 34, but if there aren't 34 lines on the screen it's just going to move down as far as it can go. And at that point in time, the last line is 5 涓洰褰?67,369,209,856 鍙敤瀛楄妭, so that's why that line is overwritten.

Yes, You are right. I ran the test codes(a little changes for the codes) on Ubuntu 20.04, , It's not pretty also.

image

In fact, 'tmp2.txt' is the output of ConPTY typing some commands after executing 'cmd.exe'.
The output looks likely not good for the test codes:

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

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);

    err = CreatePseudoConsole(COORD{ 800, 600 }, 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);


    //type command
    std::string cmdListDir = "dir\r\n";
    DWORD l;
    success = WriteFile(inPipeOurSide, cmdListDir.c_str(), cmdListDir.length(), &l, nullptr);
    assert(success && l == cmdListDir.length());

    success = WriteFile(inPipeOurSide, cmdListDir.c_str(), cmdListDir.length(), &l, nullptr);
    assert(success && l == cmdListDir.length());


    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]);
    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]);
        }
    } while (true);


    CloseHandle(pi.hProcess);

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

    ClosePseudoConsole(hPC);
    return 0;
}

image

I tried to simulate the above test and ran the PTY on Linux, print the output on Windows, It's looks every good.
image

I don't know what I missed.

@Tancen commented on GitHub (Jul 4, 2022): > I'm assuming your code page is 936, but it would help if you could confirm that, because that might be a factor (just type `chcp` at the cmd prompt to see the active code page). > > But I suspect the problem is that your screen height was set to 32 lines or less when you ran the test, because that's the only way I can reproduce the output you're seeing. > > The `\1b[34;1H` escape sequence is trying to move to line 34, but if there aren't 34 lines on the screen it's just going to move down as far as it can go. And at that point in time, the last line is `5 涓洰褰?67,369,209,856 鍙敤瀛楄妭`, so that's why that line is overwritten. Yes, You are right. I ran the test codes(a little changes for the codes) on Ubuntu 20.04, , It's not pretty also. ![image](https://user-images.githubusercontent.com/3649637/177158041-94d47df2-2d98-4933-bded-23b2cc1b24a0.png) In fact, 'tmp2.txt' is the output of ConPTY typing some commands after executing 'cmd.exe'. The output looks likely not good for the test codes: ``` #include <Windows.h> #include <stdio.h> #include <assert.h> #include <string> #include <memory> 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); err = CreatePseudoConsole(COORD{ 800, 600 }, 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); //type command std::string cmdListDir = "dir\r\n"; DWORD l; success = WriteFile(inPipeOurSide, cmdListDir.c_str(), cmdListDir.length(), &l, nullptr); assert(success && l == cmdListDir.length()); success = WriteFile(inPipeOurSide, cmdListDir.c_str(), cmdListDir.length(), &l, nullptr); assert(success && l == cmdListDir.length()); 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]); 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]); } } while (true); CloseHandle(pi.hProcess); CloseHandle(outPipeOurSide); CloseHandle(inPipeOurSide); CloseHandle(outPipePseudoConsoleSide); CloseHandle(inPipePseudoConsoleSide); ClosePseudoConsole(hPC); return 0; } ``` ![image](https://user-images.githubusercontent.com/3649637/177163696-d069be84-ac47-48f1-9554-1f788fd19202.png) I tried to simulate the above test and ran the PTY on Linux, print the output on Windows, It's looks every good. ![image](https://user-images.githubusercontent.com/3649637/177166662-828555b4-9951-48a8-a728-e414a43354de.png) I don't know what I missed.
Author
Owner

@zadjii-msft commented on GitHub (Aug 1, 2022):

Yea I mean, this definitely just looks like this was ConPTY output that was recorded at one buffer size, and then replayed at another. Theoretically, conpty should be a bit more resistant to something like that, it could maybe output something a bit more...

Hmm. Y'know, I'm pretty confident ConPTY actually should emit \r\n's at the end of lines, rather that positioning the cursor manually. At least in the case here we probably should... Hmm.

I wonder if this is just because conpty is moving two lines down. Because it's not just "on to the next line", we instead choose to optimize, and send the  instead of \r\n\r\n. You can see that other places too, like  near the top.

I suppose it actually always makes sense to optimize out "move two lines down" into nl/cr pairs. Heck, even 3 lines is equally optimal, and more literal. I wonder if there's anything horrifying that would regress if we did that.


For OP's sake: You'll probably have better results making sure that any verbatim conpty output is replayed in a terminal window of the exact same dimensions. ConPTY isn't really meant to be replayed. It's more designed to be used interactively.
@zadjii-msft commented on GitHub (Aug 1, 2022): Yea I mean, this definitely just looks like this was ConPTY output that was recorded at one buffer size, and then replayed at another. Theoretically, conpty should be a bit more resistant to something like that, it could maybe output something a bit more... Hmm. Y'know, I'm pretty confident ConPTY actually should emit `\r\n`'s at the end of lines, rather that positioning the cursor manually. At least in the case here we probably should... Hmm. I wonder if this is just because conpty is moving _two_ lines down. Because it's not just "on to the next line", we instead choose to optimize, and send the `` instead of `\r\n\r\n`. You can see that other places too, like `` near the top. I suppose it actually always makes sense to optimize out "move two lines down" into nl/cr pairs. Heck, even 3 lines is equally optimal, and more literal. **I wonder if there's anything horrifying that would regress if we did that**. <hr> For OP's sake: You'll probably have better results making sure that any verbatim conpty output is replayed in a terminal window of the exact same dimensions. ConPTY isn't really meant to be replayed. It's more designed to be used interactively.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#17822