libsixel icon indicating copy to clipboard operation
libsixel copied to clipboard

Windows: MSYS/MinGW64 builds does not generate sixel image output in native MSYS/mintty terminal

Open eabase opened this issue 4 months ago • 65 comments

I have managed to compile the code herein, using MSYS in the MINGW64 environment.

The resulting img2sixel.exe work fine, when running in Powershell (pwsh 7.5.2) within Windows Terminal. However, when using the same binary (exe), in the native MSYS/MINGW64 terminal, the code seem to run, but no output is generated.

In addition, the "debug message" seem to be always turned on against compiler (configure) specifications, so something else must be going on.

Image

This is the only output I get:

Image

For the details about compiling and the various related issues, please look at these:

  • https://github.com/saitoha/libsixel/issues/180
  • https://github.com/saitoha/libsixel/issues/197
  • https://github.com/mintty/mintty/issues/1252
  • https://github.com/hpjansson/chafa/issues/186
  • https://github.com/microsoft/terminal/issues/1173 # ConPTY Passthrough mode

Also, I'm posting some of them as back-links from those to here.


In addition, @Kreijstal mentioned:

you can build it on msys2, however there are some things that must be patched, I made a PR for the other libsixel fork, but never for this one..

Can you make a PR for the MSYS build compatibility in this repo? Also, where is your code or PR located?

🚩 I think it would better if people stop using the other now archived repo.

eabase avatar Aug 09 '25 09:08 eabase

@eabase As mentioned in the mintty issue, this problem is likely a problem with the ConPTY or Console API layer. After trying various workarounds, I found that it works fine if I expand it into a single string at the shell level. I'm not sure why.

$ echo "$(./img2sixel.exe image.png)"

saitoha avatar Aug 09 '25 20:08 saitoha

I'm not exactly sure how an MSYS shell works, but if it's like a separate OS, then it's probably opening a new conpty session in order to execute win32 executables. And when that happens, your content is getting processed by whatever version of conhost is bundled with Windows. If you're on Windows 10, that's going to be years out of date, and definitely won't be able to handle sixel. It might work on Windows 11 though.

j4james avatar Aug 09 '25 21:08 j4james

This issue is reproducible on my Windows 11 environment as well. Incidentally, it seems that builds which depend on a cygwin1.dll built under Cygwin do not exhibit this issue. Interesting.

saitoha avatar Aug 09 '25 21:08 saitoha

Findings:

  • The issue also occurs over SSH (≈ not a terminal-specific problem).
  • It does not occur with Cygwin builds (binaries linked against cygwin1.dll / msys-2.0.dll).
  • It does occur with binaries built in the MSYS2 UCRT64/MINGW64/CLANG64 environments using MinGW GCC/clang (occurs when going through ConPTY).
  • Setting the $MSYS environment variable to disable_pcon works around the problem.
Image

saitoha avatar Aug 10 '25 03:08 saitoha

To display sixel output in mintty, simply cat it to the terminal. No need to disable_pcon as the ConPTY layer. Test with MSYS, also UCRT64. (As a side-note, by the way, to display an image file like katze.jpg, you could also use the iTerm2 image protocol which mintty implements, e.g. using the showimg script from the mintty utils repo.) What Windows-compiled (or MinGW) sotware does to output sixel controls is out of control of mintty. To debug the issue, mintty logging could be activated in order to see any additional escape sequences that might be emitted.

mintty avatar Aug 10 '25 08:08 mintty

Awesome!

The weird formalism of sub-shelling the exe works in both Cygwin and MSYS/MINGW64:

echo "`./img2sixel.exe egret.jpg`"
  • cat *.six - works in all (Powershell, Cygwin, MSYS)
  • type ..\images\snake.six works in CMD
  • ./img2sixel.exe egret.jpg | cat works in MSYS
  • ./img2sixel.exe egret.jpg | cat does not work in Cygwin
  • ./img2sixel.exe egret.jpg | cat >/dev/tty works in Cygwin
  • .\img2sixel.exe egret.jpg - Works in Powershell (pwsh) and CMD in Windows Terminal
  • MSYS=disable_pcon ./img2sixel.exe egret.jpg - works in MSYS
  • MSYS=disable_pcon ./img2sixel.exe egret.jpg - does not work in Cygwin
  • CYGWIN=disable_pcon ./img2sixel.exe egret.jpg - does not work in Cygwin

@mintty Yeah, I guess we're not really interested in other terminals at this moment. Because if we can't get this to work seamless in native Win, MSYS and Cygwin, it will be hard for any other project to follow. OC we already know that just cating raw six to terminals in powershell and mintty works just fine, as I stated above. It's what's going on behind the scenes in Cygwin/Mintty Bash vs in MSYS Bash, when you run the *.exe and the output is to the terminal. I can't see a reason why these are handled differently?

From above experiments, it seem that MSYS is handling pipes differently than Cygwin... What's their connection to Windows pipes?

ls -Force '\\.\pipe\*' | Where-Object { $_.Name -match 'cygwin|msys|conpty' } | Select Mode, Length, Name, ResolvedTarget | Sort Name | ft -AutoSize
Mode  Length Name                                         ResolvedTarget
----  ------ ----                                         --------------
-----      1 conpty-1988358.795246441-in                  \\.\pipe\conpty-1988358.795246441-in
-----      1 conpty-1988358.795246441-out                 \\.\pipe\conpty-1988358.795246441-out
-----      1 conpty-3287206.9207443856-in                 \\.\pipe\conpty-3287206.9207443856-in
-----      1 conpty-3287206.9207443856-out                \\.\pipe\conpty-3287206.9207443856-out
-----      0 conpty-3287206.9207443856-out-worker         \\.\pipe\conpty-3287206.9207443856-out-worker
-----      1 conpty-3483248.876075733-in                  \\.\pipe\conpty-3483248.876075733-in
-----      1 conpty-3483248.876075733-out                 \\.\pipe\conpty-3483248.876075733-out
-----      0 conpty-3483248.876075733-out-worker          \\.\pipe\conpty-3483248.876075733-out-worker

-----      1 cygwin-e022582115c10879-25152-sigwait        \\.\pipe\cygwin-e022582115c10879-25152-sigwait
-----      1 cygwin-e022582115c10879-46776-sigwait        \\.\pipe\cygwin-e022582115c10879-46776-sigwait
-----      1 cygwin-e022582115c10879-pty0-echoloop        \\.\pipe\cygwin-e022582115c10879-pty0-echoloop
-----      1 cygwin-e022582115c10879-pty0-from-master     \\.\pipe\cygwin-e022582115c10879-pty0-from-master
-----      1 cygwin-e022582115c10879-pty0-from-master-nat \\.\pipe\cygwin-e022582115c10879-pty0-from-master-nat
-----      1 cygwin-e022582115c10879-pty0-master-ctl      \\.\pipe\cygwin-e022582115c10879-pty0-master-ctl
-----      1 cygwin-e022582115c10879-pty0-to-master       \\.\pipe\cygwin-e022582115c10879-pty0-to-master
-----      1 cygwin-e022582115c10879-pty0-to-master-nat   \\.\pipe\cygwin-e022582115c10879-pty0-to-master-nat

-----      1 msys-dd50a72ab4668b33-22476-sigwait          \\.\pipe\msys-dd50a72ab4668b33-22476-sigwait
-----      1 msys-dd50a72ab4668b33-50772-sigwait          \\.\pipe\msys-dd50a72ab4668b33-50772-sigwait
-----      1 msys-dd50a72ab4668b33-pty0-echoloop          \\.\pipe\msys-dd50a72ab4668b33-pty0-echoloop
-----      1 msys-dd50a72ab4668b33-pty0-from-master       \\.\pipe\msys-dd50a72ab4668b33-pty0-from-master
-----      1 msys-dd50a72ab4668b33-pty0-from-master-nat   \\.\pipe\msys-dd50a72ab4668b33-pty0-from-master-nat
-----      1 msys-dd50a72ab4668b33-pty0-master-ctl        \\.\pipe\msys-dd50a72ab4668b33-pty0-master-ctl
-----      1 msys-dd50a72ab4668b33-pty0-to-master         \\.\pipe\msys-dd50a72ab4668b33-pty0-to-master
-----      1 msys-dd50a72ab4668b33-pty0-to-master-nat     \\.\pipe\msys-dd50a72ab4668b33-pty0-to-master-nat

eabase avatar Aug 11 '25 02:08 eabase

Ok, I don't know WTF is going on, now all of the sudden (after having closed and reopened Cygwin), one of the above commands that didn't work previously, now works:

# Works in Cygwin ??
./img2sixel.exe egret.jpg | cat

eabase avatar Aug 11 '25 05:08 eabase

I can't see a reason why these are handled differently?

Windows-compiled programs likely use the Windows console API for their output, rather than writing to plain stdout. Then the ConPTY layer may inject whatever it thinks to be witty. Analysis of mintty log (see above) may help. Actually, there is an updated version of the ConPTY layer, just not yet deployed with Windows. You can patch it yourself, however. Would be good to know whether that helps: See the description of installing OpenConsole in https://github.com/mintty/mintty/wiki/Tips#inputoutput-interaction-with-alien-programs

mintty avatar Aug 11 '25 08:08 mintty

I spent a while digging into this and:

  • this shouldn't be related to any weirdness in the outputting program itself, my testing program ended up using NtWriteFile (through fputs)
  • CYGWIN=disable_pcon doesn't have an effect on the fly, but it does if it's set before launching the Cygwin process; I'm not sure why exactly MSYS2 differs in this, but it might be due to this change at a glance
  • running in an unwrapped ConHost just prints the sequence interpreted as characters (subject to active codepage, see Chcp) because ENABLE_VIRTUAL_TERMINAL_PROCESSING is not set
  • if the program sets ENABLE_VIRTUAL_TERMINAL_PROCESSING in its console, nothing is displayed
  • which is also what happens when run from WSL, MSYS2 or Cygwin through ConPTY
  • piping through Cat changes the result because now the output doesn't go through the console at all, it's just basic pipes
  • if the program unsets ENABLE_VIRTUAL_TERMINAL_PROCESSING in its console, we go back to printing characters, even in WSL, MSYS2 and Cygwin through ConPTY
  • the outlier here is Windows Terminal where some kind of ConPTY is involved, but the graphics is displayed, and this works regardless of setting or unsetting ENABLE_VIRTUAL_TERMINAL_PROCESSING; after skimming the Terminal source code, I agree with mintty here in that Terminal uses its own build of ConHost (OpenConsole) that somehow doesn't just eat the sequences; I don't want to fiddle with my system ConHost to test this, Terminal doesn't easily allow using system ConHost and didn't have the time to build my own version of Terminal

elieux avatar Aug 11 '25 20:08 elieux

Finding this recent change in MSYS that supports the workaround was really an act of digging, I suppose :) Thanks a lot for the analysis; the patch should be ported upstream to cygwin.

I don't want to fiddle with my system ConHost

I would test this if someone please gives build instructions for the img2sixel binary.

Terminal uses its own build of ConHost

Could you see whether Windows Terminal can use its own ConHost even if the legacy conhost is still installed in Windows? If so, I'd be highly interested in finding out how it would achieve that (so I could mimic the approach for mintty running WSL).

mintty avatar Aug 12 '25 08:08 mintty

For the test case, I just used Type or Cat with some stored Sixel file for a bit, then I put the sequences into my own simple program to avoid dealing with Cmd, source code attached: text-test.zip

I looked around WT's code a bit and it seems it's somehow convincing Condrv that it can handle both sides of the console by creating custom handles, the src/host/srvinit.cpp, src/server/DeviceHandle.cpp, and src/winconpty/winconpty.cpp files seem most relevant. I'd like to experiment with it a bit, but I don't know when I'll get to it.

Could you see whether Windows Terminal can use its own ConHost even if the legacy conhost is still installed in Windows?

What exactly do you mean by "legacy conhost is still installed in Windows"? I haven't fiddled with anything in System32 and WT seems to be happily spawning its own OpenConsole.exe instead of ConHost.exe.

elieux avatar Aug 12 '25 21:08 elieux

Your program, compiled with cygwin x86_64-w64-mingw32-gcc, works for me in both cygwin and MSYS. So how would you compile it to not work?

WT seems to be happily spawning its own OpenConsole.exe instead of ConHost.exe.

Yeah, I meant how would it achieve that without fiddling in System32?

mintty avatar Aug 12 '25 23:08 mintty

If you're asking how terminals on Windows can use a more modern version of conpty than the one included in the Windows system32 directory, you can look at the commits in other terminals that have done that.

But note that this only works when the terminal is responsible for starting the conpty session. If, for example, you've opened a WSL shell, and then run cmd.exe, that'll start a separate conpty session using the system conpty.dll, and not the one bundled with the terminal. This applies to Windows Terminal as well.

j4james avatar Aug 13 '25 00:08 j4james

IDK, if this helps, but the imcat program output what seem to be sixel codes, that works in both the WT/pwsh and Cygwin/MSYS/Bash. Although imcat itself outputs shitty images.

The compile instructions for THIS code is here:

  • https://github.com/saitoha/libsixel/issues/180#issuecomment-3168792161

The CMake compile instructions for imcat is here (and comment include a quasi patch):

  • https://github.com/stolk/imcat/issues/15

And the source code I used for the above imcat was from here:

  • https://github.com/belfner/imcat/blob/master/imcat.c

@elieux Also, I believe VT processing have been enabled by default on all Windows apps and terminals. So if you say:

if the program sets ENABLE_VIRTUAL_TERMINAL_PROCESSING in its console, nothing is displayed

That may explain some of this behavior. imcat above sets it in the program itself:

static void set_console_mode(void)
{
	DWORD mode=0;
	const HANDLE hStdout = GetStdHandle( STD_OUTPUT_HANDLE );
	GetConsoleMode( hStdout, &mode );
	mode = mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
	SetConsoleMode( hStdout, mode );
	oldcodepage = GetConsoleCP();
	SetConsoleCP( 437 );   // IBM 437 !!
	doubleres = 1;
}

However, I see they also use code page for IBM 437 which is gonna break when windows defaults to use UTF-8.

In pwsh, my terminal settings looks like this:

Image

IDK, what mintty does there, becuase it used to be that it was pwsh that was the trouble maker, and mintty always worked.

This issue shows how the fonts are not handled correctly (by imcat) in pwsh (fail) vs mintty (ok).

  • https://github.com/stolk/imcat/issues/14

You may also wanna check your terminal coding with:

[console]::InputEncoding && [console]::OutputEncoding && $OutputEncoding && chcp

eabase avatar Aug 13 '25 03:08 eabase

IDK, if this helps, but the imcat program output what seem to be sixel codes, that works in both the WT/pwsh and Cygwin/MSYS/Bash. Although imcat itself outputs shitty images.

imcat doesn't support sixel. It's just using half-block characters to create a low-res approximation of the image.

j4james avatar Aug 13 '25 23:08 j4james

@j4james

Yes, I know, but at least the output is working without weird terminal hacks. Most likely because it was compiled properly by a modern MSVC compiler, and a clearly defined Cmake file (GMakeLIsts.txt) so the resulting binary is compatible with new Windows UI APIs.

# file img2sixel.exe
PE32+ executable for MS Windows 5.02 (console), x86-64 (stripped to external PDB), 10 sections

# file imcat.exe 
PE32+ executable for MS Windows 6.00 (console), x86-64, 6 sections
  • A subsystem version of 6.02 suggests the executable uses features or APIs introduced around Windows 8.
  • A subsystem version of 5.02 indicates the executable is designed to run on older Windows versions, ensuring compatibility with systems as old as Windows XP or Server 2003.

The current config/build system is incredibly over-engineered, nearly impossible to comprehend, astronomically huge and mostly redundant to most OS's and systems today. We should be able to rewrite the entire builds for Windows, MSYS, linux with a minimal Cmake build file, updating the fairly small code base to use modern GCC and MSVC standards. Yes, I know it's not fun to hear for someone who has dedicated 10 years of hard work on a project, but it's reality. Nobody is gonna want to touch this, and surely not take maintenance responsibility of this legacy code. Sorry, but I think that is reality.

eabase avatar Aug 15 '25 02:08 eabase

Note that setting disable_pcon on the fly works when launching mintty or a new shell, so even if CYGWIN=disable_pcon img2sixel may not change the ConPTY mode, CYGWIN=disable_pcon sh -c img2sixel would do it.

For testing how changing the conhost exe might affect img2sixel, I still do not have a working counter-example. My self-built img2sixel from the libsixel download does not work at all (no output in any mode), while a version I had around previously does work, in all conhost configurations.

mintty avatar Aug 16 '25 11:08 mintty

@mintty

Note that setting disable_pcon on the fly works when launching mintty or a new shell,..

Wish I thought of that before (above). Also I think I need to update my Cygwin.

Did you test it with the above?

eabase avatar Aug 16 '25 23:08 eabase

Did you test it with the above?

Above what? As just said, I do not have a significant test scenario. If I get working instructions how to construct one, I can test whether conhost makes a difference.

mintty avatar Aug 16 '25 23:08 mintty

I now have a working CMake build that:

  • Works out-of-the-box (without hacks) on all 3 main shell environments: Powershell, MSYS MINGW64 and Cygwin.
  • It's using only 1 CMakeLists.txt file, plus a small one-click (pwsh) build.ps1 script.
  • Builds only for x64 using MSVC (cl.exe) in v.17 mode.
  • Does not include any language plugins.
  • Does not include python bindings, gcov, debug, or tests.
  • Compiled without gd and gdk-pixbuf support...although this is unclear.
  • Also require the use and installation of vcpkg in Windows (just as in MSYS/Cygwin.)

Unfortunately, I cannot do something more general because the astronomically bloated and incomprehensible collection of build scripts, prevents me from getting workable code for other build options. Including poor code folder organization. (I had to delete duplicate code and move some code to other folders.)

At the moment, the WIP is to:

  • build completely static *.exe's and a true static libsixel.dll for use in other languages.
  • Figure out all the dependencies on vcpkg for importing both the dynamic and static windows builds, where/if available.
  • Clarify the code changes
  • Clarify my CMakeLists.txt file.

[!NOTE] Apparently vcpkg is internally using MSYS for building packages! 🤦 So using vcpkg in Windows, basically tries to hide the fact that they are setting up and using MSYS, at an obvious performance (and information) loss. Building the required dependencies took ~20 min, with nearly zero info on the status of what's going on (when CPU is running @ ~95%), unlike native MSYS builds always showing progress bars etc.

I will share my fork once done, but there is no ETA.
(Which means it could take months as I'm busy with several other projects atm.)

[!IMPORTANT] The above was meant as PoC for illustrating the importance of using simple and modern build tools.
It was not meant as an insult to the developers who have invested years of hard work building this library.

In summary, if you experience broken terminal behavior across different environments, it's because you're either (a) using outdated build tools, or (b) using src code meant for outdated OS/UI, or both.

eabase avatar Aug 22 '25 11:08 eabase

In summary, if you experience broken terminal behavior across different environments, it's because you're either (a) using outdated build tools, or (b) using src code meant for outdated OS/UI, or both.

as long as the build tools are bootstrapable using open source tools, there should be no issue on my side

Kreijstal avatar Aug 22 '25 11:08 Kreijstal

Thanks for looking into this. I can reproduce the same behavior with Python installed via pacman in a MinGW/UCRT environment, so I don’t think this is a libsixel-specific issue. My understanding is that the problem lies with the process that hosts the PTY. Image

saitoha avatar Aug 25 '25 03:08 saitoha

I agree the current build script is quite large and unwieldy, but it doesn’t seem directly related to this issue. If there’s demand, I’m open to adding support for building with cl.exe.

saitoha avatar Aug 25 '25 04:08 saitoha

Here is a minimal reproducible example in C:

Image

saitoha avatar Aug 25 '25 04:08 saitoha

Maybe the c runtime itself has the bug?

Kreijstal avatar Aug 25 '25 07:08 Kreijstal

I suggest you try the python experiment with a devices attributes query (i.e. \033[c) rather than a Sixel sequence. That should make it clearer what's actually happening here.

 /bin/python -c 'print("\033[c")'
 /ucrt64/bin/python -c 'print("\033[c")'

When you're running a native MSYS2 application, the output from the app goes directly to the terminal, so you'll get a DA response back from mintty that'll be something like 64;1;2;4;6;9;11;15;21;22;28;29c.

But when you run a win32 console application, it has to go through a translation layer (conpty), so you're effectively getting a completely different terminal. Conpty first interprets the escape sequences itself, and then forwards a representation of that content to the terminal, similar to the way tmux works. If there's an escape sequence that conpty doesn't understand (like Sixel), then it's not likely to work.

The default conpty implementation on Windows 10 is incredibly old, so it'll probably respond to the DA query with something like 1;0c. The Windows 11 version is better, but apparently still doesn't include the Sixel functionality yet, so the response will probably be something like 61;6;7;14;21;22;23;24;28;32;42c.

Although once you have a version of conpty that does actually support Sixel, it should also have the new pass-through functionality. And in that case the DA query will be passed through directly to mintty, and you should get back mintty's DA response.

j4james avatar Aug 25 '25 10:08 j4james

Using the code that @elieux shared, I put together a workaround. For reasons I don’t fully understand, you need to pass the -mwindows option to the linker.

https://github.com/saitoha/libsixel/commit/6569ed6ca00024788d8761d052487357a9b88638

Image

On my machine, it also works without doing a fully static build (i.e., even without --disable-shared --enable-static). In that case, either run make install and execute the installed binary, or run the uninstalled artifacts like this:

PATH=$PATH:src/.libs converters/.libs/img2sixel.exe images/snake.png

One caveat: if you use the libtool-generated wrapper binary converters/img2sixel.exe, nothing is displayed.

saitoha avatar Aug 25 '25 10:08 saitoha

If we build with make LDFLAGS=-Wl,-static, it also removes the dependency on libwinpthread-1.dll, so we should be able to ship a single, self-contained binary.

saitoha avatar Aug 25 '25 11:08 saitoha

It appears that -mwindows has significant side effects: it disables signal handling, so Ctrl+C (and other console interrupts) no longer work.

saitoha avatar Aug 25 '25 11:08 saitoha

@j4james I’m connected to a Windows host from iTerm2 on macOS, and I received the following DA1 reply from iTerm2: Image

saitoha avatar Aug 25 '25 13:08 saitoha