terminal icon indicating copy to clipboard operation
terminal copied to clipboard

DISCUSSION: Is my app running in Terminal?

Open vefatica opened this issue 3 years ago β€’ 43 comments

I've been using

!((BOOL) SendMessage(GetConsoleWindow(), WM_GETICON, 0, 0))

Other ideas? Thanks. - Vince

vefatica avatar Aug 27 '20 17:08 vefatica

Could you clarify? What exactly are you trying to do?

zadjii-msft avatar Aug 27 '20 17:08 zadjii-msft

Looks like somebody is trying to set the icon by literally telling the window manager to replace it. Hm.

DHowett avatar Aug 27 '20 17:08 DHowett

I'm trying to find out if my app is running in Windows Terminal. I don't care about the icon. If I'm in a console, that evaluates to FALSE; in Terminal to TRUE.

vefatica avatar Aug 27 '20 17:08 vefatica

If you have a reason to detect if you are running inside terminal, we should probably have the discussion as to why you want to detect that.

Any behavior you believe to be Terminal-specific is not, I promise you, and your application will do the wrong thing.

DHowett avatar Aug 27 '20 17:08 DHowett

Didn't you (a while back) give me a list of Win32 API console functions that wouldn't work as expected in Terminal. I suppose much behavior IS Terminal-specific and I don't want my app doing the wrong thing.

vefatica avatar Aug 27 '20 17:08 vefatica

Ah, those functions you should avoid calling in general, not just "in the Windows Terminal". Those are functions that'll work less well in any ConPTY session. This includes the Windows Terminal, ssh sessions, and other terminal emulators as well, like VsCode, hyper, mintty, etc. In those other applications, you might not be able to detect that you're running in the Windows Terminal, but those APIs would still behave weirdly.

Very specifically, GetConsoleWindow is one of those APIs πŸ˜‰

zadjii-msft avatar Aug 27 '20 17:08 zadjii-msft

I wish I saw the bigger picture. I don't even know what ConPTY is. For me Windows Terminal is the only alternative to conhost. I don't know how to re-phrase my question.

vefatica avatar Aug 27 '20 17:08 vefatica

Don't worry about it, let me help however I can.

In short: The rest of the terminal world (*nix) uses streams of characters to have client applications interact with the Terminal. On windows however, client apps use the Console API to interact with the "terminal" (conhost.exe).

The Console API however is terrible and full of poorly documented, poorly supported APIs. Plus, it only works on Windows, so it's hard for commandline client apps to target both Windows and *nix OSes.

Enter ConPTY - Conpty is a magic layer that translates all the console API calls to streams of characters, so that they could be used by any terminal emulator. This means that terminal no longer needs to understand the ins and outs of the console API - conpty will do that bit for them.

Conpty is what powers the Terminal - fundamentally, the Terminal could be hooked up to a linux commandline client app, and work just fine, via a stream of characters.

This is a very broad overview, but should help explain the situation.

zadjii-msft avatar Aug 27 '20 17:08 zadjii-msft

Thanks. For me, Terminal is just a way to run command interpreters in Windows. It has features that the Windows console lacks.

My preferred command interpreter is "TCC" (grandson of "4DOS", still from JP Software). I write plugins (DLLs) to extend its capabilities. Having used the console APIs for a long time I'm pretty comfortable with them. Many things don't work in Terminal (and never will) and I'd like one of my plugins (dedicated to console-related things) to decline being loaded if TCC is running in Terminal. Hence, my question.

The SendMessage was just a gimmick. I used WM_GETICON because I saw it sent to (Terminal's version of) GetConsoleWindow().

And FWIW, WindowsTerminal.exe crashed on me a few times after being spied on with Spy++. That is not reliably reproducible.

vefatica avatar Aug 27 '20 18:08 vefatica

Many things don't work in Terminal (and never will)

What kinds of things are you doing specifically? I'd love to hear more about your use case

zadjii-msft avatar Aug 27 '20 19:08 zadjii-msft

Hmmm! I could go on for a long time. The plugin interface allows the plugin to export COMMANDs, internal _VARIABLES, variable @FUNCTIONs, and the address of a keystroke-handling function. In all, I have more than 300 items that do quite a variety of things. Here are a very few examples.

CONSIZE - set any of screen buffer size, console window size (characters), and console window position (absolute, relative, and nine canonical centered positions)

CONIMAGE/CONRESTORE - save/restore the console sceeen buffer (text and attributes),

SETICON - set the console's icon (to any from a DLL or EXE)

v:> echo %@icons[c:\windows\system32\shell32.dll] 329

v:> echo %@keytime[hkcu\environment] 2020-08-23,12:40:21.1293486

v:> echo %@up[v] 7 days 1 hour 34 minutes 17 seconds

v:> echo %@up[d] 7.06666

v:> echo %_ruler ....+....1....+....2....+....3....+....4....+....5....+....6....+....7....+....8....+....9....+....0....+....1....+....2....+...

My keyhandler's most useful function is bound to Ctrl-Del. Leaving the current prompt fixed, it erases the text above, one line at a time, while scrolling the text further-above down (to just above the command line). There is no viewport/history distinction as in Terminal so I can go right up to the top of the console screen buffer. This is very useful if I'd like to execute a sequence of commands (say to be copied and pasted into a post). If I botch one I can erase it and its output and try again. Ctrl-Shift-Del does the same 1 page at a time.

I already have a rudimentary 4WT.DLL plugin which implements Ctrl-Del = delete upwards (as best it can ... pretty easy with VT control sequences).

vefatica avatar Aug 27 '20 20:08 vefatica

I might have gotten carried away there. If you only wanted to hear about things that are or might be problematic in Terminal ...

v:> help _curchar character at mouse cursor (decimal)

I don't know if I can do that if running in Terminal. Does GetCurrentConsoleFontEx() work? I might also need Terminal's padding?????

vefatica avatar Aug 27 '20 21:08 vefatica

M. Fatica: http://jdebp.uk./FGA/capture-console-win32.html may help a little bit with the concepts and architecture.

Other people: See https://jpsoft.com/forums/threads/detect-windows-terminal.10484/ for some more context.

jdebp avatar Aug 29 '20 16:08 jdebp

Thanks, jdebp (and "hi" after many years).

vefatica avatar Aug 29 '20 16:08 vefatica

(I'm going to keep this open as a question/discussion topic.)

DHowett avatar Sep 04 '20 00:09 DHowett

After thinking about it, I'd rather ask the question "Is my app running in the Windows Console?"

  • Console applications are still running in a console by default. That means we still need the console API and it's still extensively used.
  • The console API is meant to be used along with the console.
  • It's known that console functions may behave differently if the app is running in a virtual terminal.
  • The behavior of console functions may be different depending on the terminal app used. Thus, it's not sufficient to know whether or not it was WT.
  • The only lasting statement is that the console API works for the console, right? So, if we need to branch our code we have to know if our app is running in the console. And if not, we must not rely on the console API working like in conhost.

My conclusion is that we should have an API function that clearly identifies if the console is the user interface. As a nice-to-have it may also identify which alternative terminal is used. I'm thinking about concepts like for GetFileType or GetDriveType which return identifiers. Perhaps something like a GetCliType function which returns 0 for console, greater than 0 for a terminal (e.g. 1 for WT, 2 for ... etc.), and a negative value (HRESULT?) if the function failed.

german-one avatar Sep 13 '20 15:09 german-one

This cannot be known generally. ssh.exe from Windows to sshd.exe on Windows will usually be using the normal console host (conhost) as its host, but with specific requirements.

If an application can be updated to use a new API (and fall back to non-console-host behavior when the normal console host UI isn’t in use), it could just be updated to do that all the time without checking an API and suddenly it would work better everywhere!

DHowett avatar Sep 13 '20 22:09 DHowett

I did find that when running in Windows Terminal there is a WT_SESSION environment variable defined.

HolisticDeveloper avatar Dec 13 '20 01:12 HolisticDeveloper

Yes, but WT_SESSION would be inherited if, for example, CMD, running in WT, did "start myapp" in which case myapp would be in a console.

vefatica avatar Dec 13 '20 01:12 vefatica

Clink needs to know some capabilities of the terminal host it's running inside. For example:

  1. What level of ANSI escape code support the terminal has.
  2. Whether it is safe to use SetConsoleCursorInfo to show/hide the cursor, without triggering the bug that also changes the cursor size/shape.
  3. What font rendering technology the terminal uses. For example, does the terminal render ❯ (U+276F, Heavy Right-Pointing Angle Quotation Mark Ornament) even when the selected font does not contain that glyph? Windows Terminal does; conhost and ConEmu and etc do not.

Also, does Windows Terminal run on Windows 8.1 or lower? If so, then wouldn't it bring newer terminal features to older OS versions, and then APIs like SetConsoleCursorInfo become unsafe to use even on OS versions where they would normally be safe.

The problem with deprecating console APIs and saying "don't use them" is the interop strategy is unclear. How can a console app detect whether it is running in an environment that supports the techniques that replace the console APIs? When running in an environment that doesn't support things that ConPTY/OpenConsole/etc have introduced, it's necessary to fall back to using the console APIs.

So, console programs legitimately need to be able to identify whether they are running in "the 'new' console system that is colloquially associated with Windows Terminal" versus running in some other/older console system.

(Checking WT_SESSION is unreliable because the variable gets inherited, which leads to wrong outcomes.)

chrisant996 avatar May 22 '22 17:05 chrisant996

Shouldn't the question be answered as, simply, check if $TERM is set, and if you don't want to handle the particularities of terminals, pretend all terminals comply to the modern standard of "emulate xterm-256color", and emit the proper sequences?

Diablo-D3 avatar Aug 09 '22 16:08 Diablo-D3

@Diablo-D3 no, and I'll try to share more details:

  1. Neither $TERM on *nix nor %TERM% on Windows indicate anything about the font, the cursor API quirks, or rendering capabilities (e.g. emoji substitutions, CJK width adjustments, etc).
  2. %TERM% doesn't address the %WT_SESSION% inheritance problem at all.
  3. Clink is a Windows program, and generally speaking %TERM% has no meaning for Windows programs (though it can have meaning for *nix programs ported to Windows). And so it's generally not set at all.
  4. Environment variables are inherited, and so %WT_SESSION% and %TERM% can be inherited when a console mode program running inside one terminal program spawns a new console window that gets hosted by a different terminal program -- thus causing malfunctions.

I don't believe that users should have to do a bunch of arcane configuration before programs can work reasonably (on Windows %TERM% qualifies as "arcane" because it's not a concept Windows users are really exposed to on Windows).

More importantly, I don't want to handle all the support requests when Windows users attempt to use a Windows console program like Clink and right out of the box it doesn't work at all, simply because the Windows users are using Windows and thus don't have %TERM% set. πŸ˜‰

chrisant996 avatar Aug 09 '22 17:08 chrisant996

  1. It is not the job of a program running in a terminal to know if the terminal supports any of those. Any program that wishes to support that either should argue for an OSC extension, or should have this as configurable behavior and rely on the user to know what they're doing.

  2. $WT_SESSION seems like something that third party programs shouldn't use to infer behavior. If I run clink via ssh, using Microsoft's official openssh server binary, $WT_SESSION will not be set.

  3. Having $TERM unset tells you that you're on an extremely plain terminal, and since you only target Windows, its clearly the old conhost.

  4. Your situation only makes sense if I've explicitly invoked a console program in a way that it spawns a new conhost. If I did that, I would consider it user error, as I expect all child processes that are also console programs to run in the same console.

You're right in that Windows users are unfamiliar with $TERM. Microsoft chose not to implement this 30+ years ago, and Microsoft customers have been paying for that oversight ever since. However, terminal programs in general are "arcane"; the average user on any OS (even Linux, nowadays) are going to be unfamiliar with terminals.

Diablo-D3 avatar Aug 10 '22 18:08 Diablo-D3

its clearly the old conhost.

Which, on Windows 10+, supports a whole host of control sequences... which muddies the water significantly.

DHowett avatar Aug 10 '22 21:08 DHowett

  1. It is not the job of a program running in a terminal to know if the terminal supports any of those. Any program that wishes to support that either should argue for an OSC extension, or should have this as configurable behavior and rely on the user to know what they're doing.

Almost. But Clink uses IAT hooking to extend CMD.exe. And Clink includes a built in terminal emulator for when it's running in a host that lacks support for escape sequences. And some terminal GUI hosts such as ZConsole just pass escape sequences to the underlying console. Clink needs to know a little about the environment in which it's running. %TERM% plainly does not achieve that, because it's not present.

  1. $WT_SESSION seems like something that third party programs shouldn't use to infer behavior. If I run clink via ssh, using Microsoft's official openssh server binary, $WT_SESSION will not be set.

It is the only way for a program to infer whether it is running inside Microsoft Terminal. Clink needs a reliable way to detect that. %TERM% plainly does not achieve that.

  1. Having $TERM unset tells you that you're on an extremely plain terminal, and since you only target Windows, its clearly the old conhost.

No. How did you reach that conclusion?

  • %TERM% is unset when running in Windows Terminal.
  • Clink targets all of old conhost, new conhost, Windows Terminal, ConEmu, Cmder, ZConsole, HyperTerm and so on.
  1. Your situation only makes sense if I've explicitly invoked a console program in a way that it spawns a new conhost. If I did that, I would consider it user error, as I expect all child processes that are also console programs to run in the same console.

It is not "user error" to start Windows Terminal, then start a GUI app from WT, then start a console program from the GUI app. That is a perfectly reasonable thing for a user to do, and it indeed happens.

You're right in that Windows users are unfamiliar with $TERM. Microsoft chose not to implement this 30+ years ago, and Microsoft customers have been paying for that oversight ever since. However, terminal programs in general are "arcane"; the average user on any OS (even Linux, nowadays) are going to be unfamiliar with terminals.

My point is that Clink wants to provide a seamless experience for users, and that there is no way to do so. It seems you believe that programs should not do that, and that users should not use shells without both configuring the shell and configuring the terminal in advance. I can respect that viewpoint, but I cannot require my program's users to do all that. Especially when there is no documentation or system in place for that on Windows.

chrisant996 avatar Aug 10 '22 22:08 chrisant996

I expect all child processes that are also console programs to run in the same console.

The expectation is mistaken:

  • Start Windows Terminal, with a CMD.exe tab.
  • Run start cmd.

On Windows, there are many ways for a console program to run in a new console window. There are APIs for controlling that, the start command controls that, and when a console then GUI then console the second console is separate because GUIs are not attached to any console.

chrisant996 avatar Aug 10 '22 22:08 chrisant996

With specific regard to a shell wanting to know where the Windows Terminal user interface is ...

I revisit this every now and then. I'm using the process ID of GetConsoleWindow() and a Toolhelp32Snapshot to find the parent process of OpenConsole.exe. Then I use EnumWindows() to find a CASCADIA_HOSTING_WINDOW_CLASS window belonging to that parent. It works, at least to my satisfaction, but it's a bit cumbersome.

Could you make the handle of the CASCADIA_HOSTING_WINDOW_CLASS window more readily available? One thought is to put it in the value of the WT_SESSION environment variable (along with the GUID which is (AFAICT) useless to the shell.

    `WT_SESSION=69f813f0-06ef-49d5-a64a-95359e0305c8, 0x303C0`

A shell might be looking for the existence of that variable anyway. If it's value had more useful information in it, that would be great.

Am I wrong about the GUID? Is it of any value to the shell (or to anyone)?

vefatica avatar Dec 05 '22 18:12 vefatica

Stop worrying about it. It's pretty obvious that there will be no support from the Terminal folks on this issue. I think your initial "get icon" approach is a good point to start having fun πŸ˜„ And based on that, I had some fun recently: https://github.com/german-one/termproc Now it already rings in my ears "I whouldn't if I was you ...", "Don't rely on ...". Also "But SSH ..." when people were asking about locally running applications etc. Yes, of course those work-arounds make things worse. What else but the worst can ever be expected if we don't get an easier way? 🀣

german-one avatar Dec 06 '22 17:12 german-one

What's with the whole stackoverflow attitude of "you're not supposed to do that"? Setting custom window sizes, screen buffers have been a thing on cmd since the dark ages. For an OS that touts itself so much on backward compatibility that it goes out of its way to keep even the rarest of things working the same way, this is such a stupid thing to do.

Let us have a way to detect if the script is running in Windows Terminal, we're not asking you to add features from conhost.

adithya-s-sekhar avatar Jun 05 '23 02:06 adithya-s-sekhar

I archived my old termproc repo and created a new one to reflect the new process model of Windows Terminal v. 1.18 and its feature to move tabs between windows.

The new code can be found in my termwnd repo for those who are interested in.

german-one avatar Jun 18 '23 21:06 german-one