Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

starting Terminal with wt no longer returns the main window handle of the resulting window #7084

Closed
torchgm opened this issue Jul 27, 2020 · 45 comments
Labels
Needs-Attention The core contributors need to come back around and look at this ASAP. Needs-Tag-Fix Doesn't match tag requirements Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting Resolution-By-Design It's supposed to be this way. Sometimes for compatibility reasons.

Comments

@torchgm
Copy link

torchgm commented Jul 27, 2020

Environment

Windows build number: 10.0.20175.0
Windows Terminal version (if applicable): 1.2.2022

Any other software?
N/A

Steps to reproduce

Open Windows Terminal 1.2.2022 from code that supports returning the main window handle; for example in C# .NET

using System;
using System.Diagnostics;
using System.Threading;

namespace Test
{
    class Program
    {
        static void Main(string[] args)
        {
            Process p = Process.Start("wt"); // Start Windows Terminal
            Thread.Sleep(1000); // Wait a second to make sure it's open
            Console.WriteLine(p.MainWindowHandle.ToString()); // Print handle to console
            Console.ReadLine(); // Wait for user input before finishing
        }
    }
}

Expected behavior

The handle for the subsequently opened window is returned. In the example above, a numerical value representing the window handle is printed to the console (such as 856282)

Actual behavior

A handle of 0 is returned.

@ghost ghost added Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting Needs-Tag-Fix Doesn't match tag requirements labels Jul 27, 2020
@torchgm
Copy link
Author

torchgm commented Jul 27, 2020

Alrighty, just discovered the reason for this being #6860, and fair enough it's not an API, although would it be possible to also reinstate a direct app execution alias as WindowsTerminal (or wtalias, or something like that)? It would be very-much appreciated.

@DHowett
Copy link
Member

DHowett commented Jul 27, 2020

What is your use case?

@DHowett DHowett added the Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something label Jul 27, 2020
@torchgm
Copy link
Author

torchgm commented Jul 27, 2020

Probably rather petty, but it was the only reliable way to alter its parent without user interaction and as such be able to do stuff such as use it as a desktop wallpaper:
terminal-wallpaper

It's probably possible to just start the process and then iterate through every window until I find something with the correct title but that feels super-messy and isn't very reliable.

@ghost ghost added Needs-Attention The core contributors need to come back around and look at this ASAP. and removed Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something labels Jul 27, 2020
@KalleOlaviNiemitalo
Copy link

Could reparenting instead be implemented as a command-line option similar to xterm -into windowId? Related to #4572.

@zadjii-msft
Copy link
Member

I'm just gonna put this out there, with the process model changes coming in #5000, I'm fully expecting this (officially unsupported) scenario to break further.

@torchgm
Copy link
Author

torchgm commented Jul 27, 2020

I'm well-aware this isn't supported officially in any capacity, although this same ability to set an app as a wallpaper works pretty consistently across even more complex apps; I'm not expecting it to be perfect nor would I ever ask for any changes that would solely benefit such a scenario, although it would be really nice to have a traditional alias available as well as the shim. I do however entirely understand if this isn't feasible and'll happily respect that.

@DHowett
Copy link
Member

DHowett commented Jul 27, 2020

So, okay, this is really cool and I'm sorry we broke it. We've got some changes in store that are probably going to make this worse before it makes it better. Once #5000 lands, even WindowsTerminal.exe isn't really going to have an HWND to hand out...

@zadjii-msft: gvim supports a --windowid parameter that allows for hosting. 1-10 how mad would it be if we ... did something that .. was horrible like that?

image

@torchgm
Copy link
Author

torchgm commented Jul 27, 2020

I mean something like that would be perfect! I just tried it out in vim and now I have it stuck as my wallpaper so that's fun. And no worries about breaking anything, it's to be expected! Terminal is still super-awesome anyway and I'd hate to have silly little things like this hold it back; I can't wait to see the changes you have planned come to fruition.

@DHowett
Copy link
Member

DHowett commented Jul 28, 2020

If I can suggest a somewhat obnoxious workaround for now: launching WT with --title [something you will recognize] and enumerating looking for that title might work acceptably. It's not excellent, but it is passable.

@torchgm
Copy link
Author

torchgm commented Jul 28, 2020

I'll probably end up doing that for now, thank you!

@zadjii-msft
Copy link
Member

@DHowett what the heck am I even looking at there 🙃

is gvim just, hijacking the notepad HWND? How?

I'm not sure that this would be helpful for our window/content process idea, but could be neat for embedding the window in something else (as insane as that is)

@DHowett
Copy link
Member

DHowett commented Jul 28, 2020

So, little secret here on Windows: your application window can point to a random HWND and say "yup, that's my parent!". Here, Notepad's main window is gvim's parent. I'm imagining more like, yeah, the Terminal could be embedded and literally host its entire UI in somebody else's window. It's largely orthogonal to our multi-process/content process work, and it could be related to (or the underpinnings of) "quake" mode.

@DHowett
Copy link
Member

DHowett commented Aug 13, 2020

Alright, I'm going to close this one out for now. I'd like to make sure we think about it in #5000, but there's a couple workarounds for the moment. 😄 Sorry about that.

If folks re-raise this issue once preview migrates to stable, we can reopen and redirect.

@DHowett DHowett closed this as completed Aug 13, 2020
@DHowett DHowett added the Resolution-By-Design It's supposed to be this way. Sometimes for compatibility reasons. label Aug 13, 2020
@awakecoding
Copy link

@zadjii-msft @DHowett @torchgm I am looking for the same thing, but after reading this issue that has been closed, I am not sure what is the proper way to go about detecting the window id for reparenting it. I did a quick test in PowerShell using Start-Process, and while I can grab MainWindowHandle on pwsh.exe, the same property is empty for wt.exe:

PS C:\Users\mamoreau> $pwsh_proc = Start-Process -PassThru -FilePath pwsh.exe
PS C:\Users\mamoreau> $pwsh_proc.MainWindowHandle
986958
PS C:\Users\mamoreau> $wt_proc = Start-Process -PassThru -FilePath wt.exe
PS C:\Users\mamoreau> $wt_proc.MainWindowHandle
PS C:\Users\mamoreau>

This should be equivalent to the technique used in the C# sample code of this ticket. I am running Windows Terminal 1.5.10271.0 and the ticket speaks of a regression that was first observed in 1.2.2022. Is this fixed upstream but not released yet, or is there a different method of detection offered to find the window id for reparenting? It's not clear to me if it's been fixed, or if it is going to be fixed, how, and when.

@zadjii-msft
Copy link
Member

Ah, to clarify - this was broken in ~v1.2, and we're about to break this even more before we fix it any time soon.

Reparenting the window isn't a currently supported scenario, so any workarounds we give you is certain to break again in the (near) future. I suppose we can use this thread for a discussion of how we might offer that in the future? I suppose it might help to have more details about what exactly you're trying to accomplish here. There's probably a difference between "we just want a single pane embedded in another HWND" vs "We want a straight up whole Windows Terminal instance in another HWND".

@torchgm
Copy link
Author

torchgm commented Feb 8, 2021

Personally for my use-case I'd prefer the ability to acquire the handle of then reparent the entire WT window, as this is a bit more versatile and means I can avoid making any particularly complex changes just to support it.

@awakecoding
Copy link

@zadjii-msft ok, in this case it means it's worth creating an official feature request for Windows Terminal reparenting/embedding into a parent window. This is useful to implement an "embedded mode" for a product like Remote Desktop Manager where the user doesn't want multiple separate windows, but multiple tabs inside the same program. For instance, you may have a few tabs with RDP connections alongside a few terminal tabs. System administrators have a lot of diverse connection types, and those that can be hosted inside Windows Terminal are just a small fraction of them.

Here is a screenshot showing powershell.exe reparented inside Remote Desktop Manager. We launch powershell.exe, then wait until we can grab Process.MainWindowHandle and reparent it inside our own tab. It works well, the only downside is the powershell.exe terminal will briefly appear separately in between the time it has been created and the time it has been reparented. A better way would be to pass a command-line parameter with the parent window id, which is not possible AFAIK with most programs, but it doesn't mean we couldn't try doing it for Windows Terminal.

image

@awakecoding
Copy link

@torchgm @zadjii-msft I installed Windows Terminal 1.1, an older version that still had a working Process.MainWindowHandle and I was able to reparent it inside Remote Desktop Manager. I circled it in red so you can see it, but our own tab control hides most of the Windows Terminal tab control, we can see a little bit of it, and I was able to create new Windows Terminal tabs, or trigger the dropdown selector for a new tab.

For this type of integration, the simplest would be to restore Process.MainWindowHandle + add a wt.exe command-line parameter to disable the Windows Terminal tab control, and effectively force it into a "single window mode" where tabs are disabled. Rather than use the Windows Terminal tabs, we would use our own tabs in Remote Desktop Manager and launch multiple instances of Windows Terminal instead, one per tab.

One may wonder why it would still be interesting to use Windows Terminal without its tabs: well, it does a whole lot more than tabs, it has better clipboard integration and is vastly more customizable than the old PowerShell terminal or command prompt.

image

@DHowett
Copy link
Member

DHowett commented Feb 8, 2021

The configuration you're looking for is "alwaysShowTabs": false, "showTabsInTitlebar": false.

This window (in its default configuration) is not designed to be re-parented, and I'm not terribly interested in redesigning our application to serve such a narrow use case. I appreciate what you're trying to do here, but it's just not tenable that we should open the floodgates to an endless march of features so niche.

When we have content processes and swap chain panel hosting, perhaps the calculus here changes.

@zadjii-msft
Copy link
Member

I think the vastly better solution would be to have a re-usable "terminal" control that could be hosted by other applications, rather than trying to re-host our entire application in another HWND. If only we had considered this scenario as a part of the design of the Terminal 😜.

@awakecoding What UI framework are you using for Remote Desktop Connection? WPF? There might be a better solution here.

@KalleOlaviNiemitalo
Copy link

A better way would be to pass a command-line parameter with the parent window id

I wonder how hard it would be to add such a feature to conhost. That should bypass any UWP complications, at least.

@awakecoding
Copy link

awakecoding commented Feb 8, 2021

@DHowett don't be so quick to dismiss this feature request, it's more common and important than you might think. Windows Terminal is becoming the new default terminal on Windows, so instead of pushing ISVs to reimplement their clunky terminal control, you probably want them to reuse Windows Terminal as their go-to solution. I found out I am not the first to inquire about this. @RickStrahl @StefanKoell

This being said, simple window reparenting would be good enough for quick and dirty integration, and we'd be happy with it. The ideal solution is what @zadjii-msft suggested: a proper WPF control, which would be the logical equivalent of a WebView control for a browser, except we're embedding Windows Terminal and not a browser window. A WPF control would be an acceptable solution to us, and it would also be a better one in the long run because it is more flexible than a simple window reparenting.

@awakecoding
Copy link

@KalleOlaviNiemitalo I looked at how Windows Terminal handles its tabs in Process Explorer, and it appears to spawn one OpenConsole.exe subprocess with a bunch of command-line parameters to host each tab. Quick like that, I couldn't find a way to launch it in a way that would give me a usable Process.MainWindowHandle, but if this is what I think it is (just the tab without the Windows Terminal multi-tab management on top), then it might be a very interesting approach to hosting individual tabs inside a parent process. This is probably not exposed externally, but if it's possible to make a quick & dirty prototype with usable reparenting, I'd be willing to give it a shot.

@DHowett
Copy link
Member

DHowett commented Feb 8, 2021

if this is what I think it is

It really isn't.

OpenConsole is a copy of C:\windows\system32\conhost.exe, the same console host that ships with Windows, built out of this repository. It's running in a mode where it outputs only text (control sequences) and consumes only text (also control sequences) and does not present any UI.

Everything relating to the act of being a terminal is entirely hosted within the WindowsTerminal.exe process.

@zadjii-msft
Copy link
Member

See, now I'm much more amenable to productizing the WPF control than I am to productizing "reparent the whole terminal". That's something that would need to get properly prioritized. The control already exists, but it's only for VS to consume right now - which gives us a lot of liberty in changing the interface at will.

We've always meant to productize both the WPF and UWP controls at some point. We've got some work planned for the next couple months that is certainly going to affect the API layer for them. Maybe once #5000 / #1256 is sorted out entirely, we can loop back around on this.

I'm re-purposing #6999 to track the work of productizing the controls. It's unfortunately not something we'll have time for in the 2.0 timeframe, but it's something we can certainly come back around on.

@awakecoding
Copy link

if this is what I think it is

It really isn't.

OpenConsole is a copy of C:\windows\system32\conhost.exe, the same console host that ships with Windows, built out of this repository. It's running in a mode where it outputs only text (control sequences) and consumes only text (also control sequences) and does not present any UI.

Everything relating to the act of being a terminal is entirely hosted within the WindowsTerminal.exe process.

Oh well, it was worth a shot then! No problem, the WPF approach looks like the best one anyway.

@awakecoding
Copy link

@zadjii-msft @DHowett how much work would it take to restore Process.MainWindowHandle on the latest development builds? At a bare minimum, it may be usable for an intermediate integration where we'd just ship a copy of Windows Terminal and implement the embedded mode while waiting for the WPF control.

@DHowett
Copy link
Member

DHowett commented Feb 8, 2021

Restoring the window handle with our architecture is impossible. It's not something we removed, it's something that disappeared because we had to introduce a shim executable to make the rest of the app platform work.

If we remove that shim, a couple other things fail -- mainly, we would lose the ability to launch Terminal elevated with Ctrl+Shift+Enter from the Run dialog and PowerShell/cmd would go back to hanging when you run wt from them. There's more information in the commit that introduced the shim, 592c634.

On the balance, with doing both simultaneously being impossible, I'd prefer elevation and powershell to work correctly.

@zadjii-msft
Copy link
Member

If we restore it for 1.7, there's a 99% chance I'm going to break it again in 1.8 😅.

Part of the trick here is that wt.exe is actually just launching windowsterminal.exe. App execution aliases are weird. Turns out the OS does not love having the app execution alias wt.exe point at a binary named windowsterminal.exe. So yea, wt just launches windowsterminal, which is why wt doesn't have a window handle. windowsterminal.exe does.

If you're just going to ship a copy of the entire Terminal in your application, you might as well just launch the windowsterminal.exe in the package instead of the execution alias wt.exe. (DISCLAIMER: I have no idea if that would work, and that suggestion is about as far as I'm willing to go for supporting that scenario)

@awakecoding
Copy link

@zadjii-msft I just made it work using Windows Terminal 1.1, but now you're making me wonder if I can get it working with the very latest. I basically extracted an MSIX bundle and worked with the files directly. I modified settings.json to disable the tabs and it does exactly what I want, now if only I could control which settings.json files it would use to avoid polluting the global configuration, and if I could get a recent version of Windows Terminal, it would be perfect

image

@awakecoding
Copy link

awakecoding commented Feb 8, 2021

@zadjii-msft reparenting works if I launch WindowsTerminal.exe directly instead of wt.exe, even with the latest version! I extracted the files directly to do it, but all I would need to work around this problem is a trick to detect the full path to the real WindowsTerminal.exe and use it instead of wt.exe. That's something I can live with, I'd be surprised if I can't find a way to do that.

Now the only thing missing for me are ways to override the settings.json for the embedded mode only without affecting global configuration. Are there environment variables or command-line parameters to force using a special settings.json file? I need to override things like alwaysShowInTabs, showTabsInTitlebar, etc. Ideally all of it would be controlled from the parent program, independently from the configuration used by the external Windows Terminal. Does the working directory of WindowsTerminal.exe have any impact on this?

@torchgm
Copy link
Author

torchgm commented Feb 8, 2021

Running windowsterminal.exe if obtained from the Microsoft Store isn't possible unfortunately, it just throws an Access Denied error (as a normal user, as an administrator and from code as both). Assuming that the Store will be how Windows Terminal will be distributed primarily (nothing wrong with this, I quite appreciate the Store) then its unfortunately not a particularly viable solution 😢

@DHowett
Copy link
Member

DHowett commented Feb 8, 2021

Access denied

this isn't unexpected. The directory Terminal lives in isn't intended to have its executables run directly (and this isn't a limitation we can overcome, sorry!)

@torchgm
Copy link
Author

torchgm commented Feb 8, 2021

Oh no I'm totally aware of this being a limitation, just wanted to make sure it was clear for anyone else reading through this that it won't work for Store releases.

@awakecoding
Copy link

@torchgm @DHowett @zadjii-msft yeah, I didn't know about this limitation. I found a way to detect the full path, but I hit the access denied error. It's a bummer, but a full copy of the files outside of the MSIX install location would still work.

PS > Join-Path $($(Get-AppxPackage -Name Microsoft.WindowsTerminal).InstallLocation) WindowsTerminal.exe
C:\Program Files\WindowsApps\Microsoft.WindowsTerminal_1.5.10271.0_x64__8wekyb3d8bbwe\WindowsTerminal.exe

Any hints about controlling the settings.json when launching WindowsTerminal.exe, assuming I've got a working copy that won't throw the access denied error?

@sylveon
Copy link

sylveon commented Feb 8, 2021

Instead of trying to hack around the existing terminal app, why not copy the WPF control's source in your own code and consume that? This way it should work and you're not going to be broken by changes they make until stabilization.

@awakecoding
Copy link

@sylveon I'm not so good with C# and WPF myself, but can you point me to the parts of the sources that have the WPF control? Is it actually fully usable today, just not officially documented and published?

@awakecoding
Copy link

I know this looks like a dirty hack that will make most people in this thread cringe, but it's actually perfectly acceptable and would take very few code changes on our side to implement. I can detect Windows Terminal + copy the install location to a temporary directory to avoid the "access denied" error, see the end result:

image

Last item to check on my list is how to launch WindowsTerminal.exe and tell it where to read settings.json, such that I can feed it a modified configuration where all tabs are disabled.

@sylveon
Copy link

sylveon commented Feb 8, 2021

It's here: https://github.com/microsoft/terminal/tree/main/src/cascadia/WpfTerminalControl

@awakecoding
Copy link

I tried finding a way to force WindowsTerminal.exe to read settings.json from a given location, but it doesn't seem possible:
https://github.com/microsoft/terminal/blob/main/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp

I did notice, however, that it uses a different path when inside the MSIX installation location as opposed to outside of it.

C:\Users\mamoreau\AppData\Local\Microsoft\Windows Terminal\settings.json
C:\Users\mamoreau\AppData\Local\Packages\Microsoft.WindowsTerminal_8wekyb3d8bbwe\LocalState\settings.json

Did I miss the way to tell where to load its settings? Being able to tell an application where to read its files is a common thing, and it definitely makes it much easier for testing with temporary settings that won't pollute the real ones.

@awakecoding
Copy link

@sylveon @zadjii-msft how usable is the WPF control at this point? @StefanKoell reported not so long ago having tried it without much success, as it wasn't ready for prime-time yet. I know a WPF control is considered proper, but I would still very much like to do the simple window reparenting integration, because unlike the WPF control, it doesn't require shipping a full copy of Windows Terminal. I think both are complementary, not mutually exclusive.

Control over the settings.json file to embed Windows Terminal inside a parent application is an issue that would affect both window reparenting and the WPF control integration, but I couldn't find a corresponding feature request ticket for it. Even with a WPF control, it would be a whole lot better for the parent application to simply inject its own settings.json containing global configuration and preconfigured profiles. This would be the only way to handle storing profile configurations inside a password manager or any other type of application. It wouldn't make sense to overwrite the existing settings.json with temporary configuration, so the most straightforward approach would be to find a way to override the location of settings.json with a command-line parameter or an environment variable.

Is there such a feature request already? This is beneficial to both approaches to embedding Windows Terminal inside a parent application. From a quick look at the code, it only looks for settings.json inside two predetermined paths: one when inside an MSIX package, and one when "unpacked" or outside an MSIX package. It looks for LocalAppData with the following call, and I am not aware of a trick to force it to return something else (changing $Env:LocalAppData didn't work, but it was worth a shot):

SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_FORCE_APP_DATA_REDIRECTION, nullptr, &localAppDataFolder)

A simple environment variable to force WindowsTerminal.exe to use a given directory for its settings would be enough to solve this problem. All we'd need to do is generate the settings.json on-the-fly and tell WindowsTerminal.exe to use it.

@awakecoding
Copy link

I found two related issues related to better control over the settings.json file location:

#4566
#2933

@oising any thoughts on this?

@sylveon
Copy link

sylveon commented Feb 9, 2021

I don't know how usable it is as I don't write WPF

@StefanKoell
Copy link

This weekend I tried to play around a bit and realized that the WpfControl I tried a couple of months ago wasn't the one which can now be found here:
https://github.com/microsoft/terminal/tree/main/src/cascadia/WpfTerminalControl

So I cloned this repo and tried again with the new WpfTerminalControl which seems to work different as the one I used before. I seems to be a thin managed wrapper to the actual terminal. Maybe this approach is better than the one I tried.

Anyway, I wasn't really able to run the test project:
https://github.com/microsoft/terminal/tree/main/src/cascadia/WpfTerminalTestNetCore

I was able to compile Windows Terminal using the PowerShell build scripts but I couldn't run the test app. I also wasn't able to find information on how it can be run. Are there any instructions out there?

@awakecoding
Copy link

awakecoding commented Nov 25, 2022

@DHowett I'm working on my own Windows Terminal distribution (unpackaged MSIX in a nuget package), and launching WindowsTerminal.exe works with reparenting. Now I want to avoid *re-*parenting to simply provide the parent window handle to be used initially, but I'm having trouble figuring out how the primary window gets created through all the modern WinRT stuff.

I've used Spy++ to see what the window hierarchy looked like once reparented within RDM:
image

I've searched for code for calls to CreateWindow, but I'm having trouble finding which one is supposed to be the primary window used by Windows Terminal. Can you give me a hint as to what it would be called within the WT source code? Thanks!

EDIT: Of course, I had to find it right after asking question. Is it the "Island Window" in https://github.com/microsoft/terminal/blob/main/src/cascadia/WindowsTerminal/IslandWindow.cpp?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Needs-Attention The core contributors need to come back around and look at this ASAP. Needs-Tag-Fix Doesn't match tag requirements Needs-Triage It's a new issue that the core contributor team needs to triage at the next triage meeting Resolution-By-Design It's supposed to be this way. Sometimes for compatibility reasons.
Projects
None yet
Development

No branches or pull requests

7 participants