зеркало из https://github.com/microsoft/terminal.git
One process to rule them all (#14843)
## Summary _In the days of old, the windows were sundered, each with its own process, like the scattered stars in the sky. But a new age hath dawned, for all windows now reside within a single process, like the bright gems of a single crown._ _And lo, there came the `WindowEmperor`, a new lord to rule over the global state, wielding the power of hotkeys and the beacon of the notification icon. The `WindowManager` was cast aside, no longer needed to seek out other processes or determine the `Monarch`._ _Should the `WindowEmperor` determine that a new window shall be raised, it shall set forth a new thread, born from the ether, to govern this new realm. On the main thread shall reside a message loop, charged with the weighty task of preserving the global state, guarded by hotkeys and the beacon of the notification icon._ _Each window doth live on its own thread, each guarded by the new `WindowThread`, a knightly champion to hold the `TerminalWindow`, `AppHost`, and `IslandWindow` in its grasp. And so the windows shall run free, no longer burdened by their former ways._ All windows are now in a single process, rather than in one process per window. We'll add a new `WindowEmperor` class to manage global state such as hotkeys and the notification icon. The `WindowManager` has been streamlined and no longer needs to connect to other processes or determine a new `Monarch`. Each window will run on its own thread, using the new `WindowThread` class to encapsulate the thread and manage the `TerminalWindow`, `AppHost`, and `IslandWindow`. * Related to #5000 * Related to #1256 ## Windows Terminal Process Model 3.0 Everything is now one process. All the windows for a single Terminal instance live in a singular Terminal process. When a new terminal process launches, it will still attempt to communicate with an existing one. If it finds one, it'll pass the commandline to that process and exit. Otherwise, it'll become the "monarch" and create a new window. We'll introduce a new abstraction here, called the `WindowEmperor`. `Monarch` & `Peasant` will still remain, for facilitating cross-window communication. The Emperor will allow us to have a single dedicated class for all global state, and will now always represent the "monarch" (rather than our previously established non-deterministic monarchy to elevate a random peasant to the role of monarch). We still need to do a very minimal amount of x-proc calls. Namely, one right on startup, to see if another `Terminal.exe` was already running. If we find one, then we toss our commandline at it and bail. If we don't, then we need to `CoRegister` the Monarch still, to prepare for subsequent launches to send commands to us. `WindowManager` takes the most changes here. It had a ton of logic to redundantly attempt to connect to other monarchs of other processes, or elect a new one. It doesn't need to do any of that anymore, which is a pretty dramatic change to that class. This creates the opportunity to move some lifetime management around. We've played silly games in the past trying to have individual windows determine if they're the singular monarch for global state. `IslandWindow`s no longer need to track things like global hotkeys or the notification icon. The emperor can do that - there's only ever one emperor. It can also own a singular copy of the settings model, and hand out references to each other thread. Each window lives on separate threads. We'll need to separately initialize XAML islands for each thread. This is totally fine, and actually supported these days. We'll use a new class called `WindowThread` to encapsulate one of these threads. It'll be responsible for owning the `TerminalWindow`, `AppHost` and `IslandWindow` for a single thread. This introduces new classes of bugs we'll need to worry about. It's now easier than ever to have "wrong thread" bugs when interacting with any XAML object from another thread. A good case in point - we used to stash a `static` `Brush` in `Pane`, for the color of the borders. We can't do that anymore! The first window will end up stashing a brush from its thread. So now when a second window starts, the app explodes, because the panes of that window try to draw their borders using a brush from the wrong thread. _Another fun change_: The keybinding labels of the command palette. `TerminalPage` is the thing that ends up expanding iterable `Command`s. It does this largely with copies - it makes a new `map`, a new `vector`, copies the `Command`s over, and does the work there before setting up the cmdpal. Except, it's not making a copy of the `Command`s, it's making a copy of the `vector`, with winrt objects all pointing at the `Command` objects that are ultimately owned by `CascadiaSettings`. This doesn't matter if there's only one `TerminalPage` - we'll only ever do that once. However, now there are many Pages, on different threads. That causes one `TerminalPage` to end up expanding the subcommands of a `Command` while another `TerminalPage` is ALSO iterating on those subcommands. _Emperor message window_: The Emperor will have its own HWND, that's entirely unrelated to any terminal window. This window is a `HWND_MESSAGE` window, which specifically cannot be visible, but is useful for getting messages. We'll use that to handle the notification icon and global hotkeys. This alleviates the need for the IslandWindow to raise events for the tray icon up to the AppHost to handle them. Less plumbing=more good. ### Class ownership diagram _pretend that I know UML for a second_: ```mermaid classDiagram direction LR class Monarch class Peasant class Emperor class WindowThread class AppHost Monarch "1" --o "*" Peasant: Tracks Emperor --* "1" AppLogic: Monarch <..> "1" Emperor Peasant "1" .. "1" WindowThread Emperor "1" --o "*" WindowThread: Tracks WindowThread --* AppHost AppHost --* IslandWindow AppHost --* TerminalWindow TerminalWindow --* TerminalPage ``` * There's still only one `Monarch`. One for the Terminal process. * There's still many `Peasant`s, one per window. * The `Monarch` is no longer associated with a window. It's associated with the `Emperor`, who maintains _all_ the Terminal windows (but is not associated with any particular window) * It may be relevant to note: As far as the `Remoting` dll is concerned, it doesn't care if monarchs and peasants are associated with windows or not. Prior to this PR, _yes_, the Monarch was in fact associated with a specific window (which was also associated with a window). Now, the monarch is associated with the Emperor, who isn't technically any of the windows. * The `Emperor` owns the `App` (and by extension, the single `AppLogic` instance). * Each Terminal window lives on its own thread, owed by a `WindowThread` object. * There's still one `AppHost`, one `IslandWindow`, one `TerminalWindow` & `TerminalPage` per window. * `AppLogic` hands out references to its settings to each `TerminalWindow` as they're created. ### Isolated Mode This was a bit of a tiny brainstorm Dustin and I discussed. This is a new setting introduced as an escape watch from the "one process to rule them all" model. Technically, the Terminal already did something like this if it couldn't find a `Monarch`, though, we were never really sure if that hit. This just adds a setting to manually enable this mode. In isolated mode, we always instantiate a Monarch instance locally, without attempting to use the `CoRegister`-ed one, and we _never_ register one. This prevents the Terminal from talking with other windows. * Global hotkeys won't work right * Trying to run commandlines in other windows (`wt -w foo`) won't work * Every window will be its own process again * Tray icon behavior is left undefined for now. * Tab tearout straight-up won't work. ### A diagram about settings This helps explain how settings changes get propagated ```mermaid sequenceDiagram participant Emperor participant AppLogic participant AppHost participant TerminalWindow participant TerminalPage Note Right of AppLogic: AL::ReloadSettings AppLogic ->> Emperor: raise SettingsChanged Note left of Emperor: E::...GlobalHotkeys Note left of Emperor: E::...NotificationIcon AppLogic ->> TerminalWindow: raise SettingsChanged<br>(to each window) AppLogic ->> TerminalWindow: AppLogic ->> TerminalWindow: Note right of TerminalWindow: TW::UpdateSettingsHandler Note right of TerminalWindow: TW::UpdateSettings TerminalWindow ->> TerminalPage: SetSettings TerminalWindow ->> AppHost: raise SettingsChanged Note right of AppHost: AH::_HandleSettingsChanged ```
This commit is contained in:
Родитель
bee22f3ec8
Коммит
b9248fa903
|
@ -159,6 +159,7 @@ rcx
|
|||
REGCLS
|
||||
RETURNCMD
|
||||
rfind
|
||||
RLO
|
||||
ROOTOWNER
|
||||
roundf
|
||||
RSHIFT
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"C_Cpp.loggingLevel": "None",
|
||||
"files.associations": {
|
||||
"xstring": "cpp",
|
||||
"*.idl": "cpp",
|
||||
"*.idl": "midl3",
|
||||
"array": "cpp",
|
||||
"future": "cpp",
|
||||
"istream": "cpp",
|
||||
|
@ -106,4 +106,4 @@
|
|||
"**/packages/**": true,
|
||||
"**/Generated Files/**": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1822,7 +1822,7 @@
|
|||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the theme. This will be displayed in the settings UI.",
|
||||
"not": {
|
||||
"not": {
|
||||
"enum": [ "light", "dark", "system" ]
|
||||
}
|
||||
},
|
||||
|
@ -2092,6 +2092,11 @@
|
|||
"description": "When set to true, the terminal will focus the pane on mouse hover.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"compatibility.isolatedMode": {
|
||||
"default": false,
|
||||
"description": "When set to true, Terminal windows will not be able to interact with each other (including global hotkeys, tab drag/drop, running commandlines in existing windows, etc.). This is a compatibility escape hatch for users who are running into certain windowing issues.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"copyFormatting": {
|
||||
"default": true,
|
||||
"description": "When set to `true`, the color and font formatting of selected text is also copied to your clipboard. When set to `false`, only plain text is copied to your clipboard. An array of specific formats can also be used. Supported array values include `html` and `rtf`. Plain text is always copied.",
|
||||
|
|
|
@ -98,6 +98,26 @@ namespace TerminalAppLocalTests
|
|||
}
|
||||
}
|
||||
}
|
||||
void _logCommands(winrt::Windows::Foundation::Collections::IVector<Command> commands, const int indentation = 1)
|
||||
{
|
||||
if (indentation == 1)
|
||||
{
|
||||
Log::Comment((commands.Size() == 0) ? L"Commands:\n <none>" : L"Commands:");
|
||||
}
|
||||
for (const auto& cmd : commands)
|
||||
{
|
||||
Log::Comment(fmt::format(L"{0:>{1}}* {2}",
|
||||
L"",
|
||||
indentation,
|
||||
cmd.Name())
|
||||
.c_str());
|
||||
|
||||
if (cmd.HasNestedCommands())
|
||||
{
|
||||
_logCommandNames(cmd.NestedCommands(), indentation + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void SettingsTests::TestIterateCommands()
|
||||
|
@ -164,14 +184,15 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile0");
|
||||
auto command = expandedCommands.GetAt(0);
|
||||
VERIFY_ARE_EQUAL(L"iterable command profile0", command.Name());
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -189,7 +210,8 @@ namespace TerminalAppLocalTests
|
|||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile1");
|
||||
auto command = expandedCommands.GetAt(1);
|
||||
VERIFY_ARE_EQUAL(L"iterable command profile1", command.Name());
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -207,7 +229,8 @@ namespace TerminalAppLocalTests
|
|||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile2");
|
||||
auto command = expandedCommands.GetAt(2);
|
||||
VERIFY_ARE_EQUAL(L"iterable command profile2", command.Name());
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -287,14 +310,16 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"Split pane, profile: profile0");
|
||||
auto command = expandedCommands.GetAt(0);
|
||||
VERIFY_ARE_EQUAL(L"Split pane, profile: profile0", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -312,7 +337,9 @@ namespace TerminalAppLocalTests
|
|||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"Split pane, profile: profile1");
|
||||
auto command = expandedCommands.GetAt(1);
|
||||
VERIFY_ARE_EQUAL(L"Split pane, profile: profile1", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -330,7 +357,9 @@ namespace TerminalAppLocalTests
|
|||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"Split pane, profile: profile2");
|
||||
auto command = expandedCommands.GetAt(2);
|
||||
VERIFY_ARE_EQUAL(L"Split pane, profile: profile2", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -412,14 +441,16 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile0");
|
||||
auto command = expandedCommands.GetAt(0);
|
||||
VERIFY_ARE_EQUAL(L"iterable command profile0", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -437,7 +468,9 @@ namespace TerminalAppLocalTests
|
|||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile1\"");
|
||||
auto command = expandedCommands.GetAt(1);
|
||||
VERIFY_ARE_EQUAL(L"iterable command profile1\"", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -455,7 +488,9 @@ namespace TerminalAppLocalTests
|
|||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command profile2");
|
||||
auto command = expandedCommands.GetAt(2);
|
||||
VERIFY_ARE_EQUAL(L"iterable command profile2", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -527,14 +562,15 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
|
||||
|
||||
auto rootCommand = expandedCommands.Lookup(L"Connect to ssh...");
|
||||
auto rootCommand = expandedCommands.GetAt(0);
|
||||
VERIFY_IS_NOT_NULL(rootCommand);
|
||||
VERIFY_ARE_EQUAL(L"Connect to ssh...", rootCommand.Name());
|
||||
auto rootActionAndArgs = rootCommand.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(rootActionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, rootActionAndArgs.Action());
|
||||
|
@ -621,14 +657,16 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
|
||||
|
||||
auto grandparentCommand = expandedCommands.Lookup(L"grandparent");
|
||||
auto grandparentCommand = expandedCommands.GetAt(0);
|
||||
VERIFY_IS_NOT_NULL(grandparentCommand);
|
||||
VERIFY_ARE_EQUAL(L"grandparent", grandparentCommand.Name());
|
||||
|
||||
auto grandparentActionAndArgs = grandparentCommand.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(grandparentActionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, grandparentActionAndArgs.Action());
|
||||
|
@ -744,17 +782,22 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
|
||||
|
||||
for (auto name : std::vector<std::wstring>({ L"profile0", L"profile1", L"profile2" }))
|
||||
const std::vector<std::wstring> profileNames{ L"profile0", L"profile1", L"profile2" };
|
||||
for (auto i = 0u; i < profileNames.size(); i++)
|
||||
{
|
||||
winrt::hstring commandName{ name + L"..." };
|
||||
auto command = expandedCommands.Lookup(commandName);
|
||||
const auto& name{ profileNames[i] };
|
||||
winrt::hstring commandName{ profileNames[i] + L"..." };
|
||||
|
||||
auto command = expandedCommands.GetAt(i);
|
||||
VERIFY_ARE_EQUAL(commandName, command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -880,14 +923,16 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
|
||||
|
||||
auto rootCommand = expandedCommands.Lookup(L"New Tab With Profile...");
|
||||
auto rootCommand = expandedCommands.GetAt(0);
|
||||
VERIFY_IS_NOT_NULL(rootCommand);
|
||||
VERIFY_ARE_EQUAL(L"New Tab With Profile...", rootCommand.Name());
|
||||
|
||||
auto rootActionAndArgs = rootCommand.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(rootActionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::Invalid, rootActionAndArgs.Action());
|
||||
|
@ -982,13 +1027,16 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size());
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(0u, settings.Warnings().Size());
|
||||
VERIFY_ARE_EQUAL(1u, expandedCommands.Size());
|
||||
|
||||
auto rootCommand = expandedCommands.Lookup(L"New Pane...");
|
||||
auto rootCommand = expandedCommands.GetAt(0);
|
||||
VERIFY_IS_NOT_NULL(rootCommand);
|
||||
VERIFY_ARE_EQUAL(L"New Pane...", rootCommand.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(rootCommand);
|
||||
auto rootActionAndArgs = rootCommand.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(rootActionAndArgs);
|
||||
|
@ -1205,8 +1253,8 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_ARE_EQUAL(L"${scheme.name}", realArgs.TerminalArgs().Profile());
|
||||
}
|
||||
|
||||
auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes());
|
||||
_logCommandNames(expandedCommands.GetView());
|
||||
const auto& expandedCommands{ settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
_logCommands(expandedCommands);
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, expandedCommands.Size());
|
||||
|
||||
|
@ -1215,7 +1263,9 @@ namespace TerminalAppLocalTests
|
|||
// just easy tests to write.
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command Campbell");
|
||||
auto command = expandedCommands.GetAt(0);
|
||||
VERIFY_ARE_EQUAL(L"iterable command Campbell", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -1233,7 +1283,9 @@ namespace TerminalAppLocalTests
|
|||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command Campbell PowerShell");
|
||||
auto command = expandedCommands.GetAt(1);
|
||||
VERIFY_ARE_EQUAL(L"iterable command Campbell PowerShell", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
@ -1251,7 +1303,9 @@ namespace TerminalAppLocalTests
|
|||
}
|
||||
|
||||
{
|
||||
auto command = expandedCommands.Lookup(L"iterable command Vintage");
|
||||
auto command = expandedCommands.GetAt(2);
|
||||
VERIFY_ARE_EQUAL(L"iterable command Vintage", command.Name());
|
||||
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.ActionAndArgs();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "ProposeCommandlineResult.h"
|
||||
|
||||
#include "Monarch.g.cpp"
|
||||
#include "WindowRequestedArgs.g.cpp"
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
using namespace winrt;
|
||||
|
@ -658,6 +659,13 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
if (targetWindow == WindowingBehaviorUseNone)
|
||||
{
|
||||
// In this case, the targetWindow was UseNone, which means that we
|
||||
// want to make a message box, but otherwise not make a Terminal
|
||||
// window.
|
||||
return winrt::make<Remoting::implementation::ProposeCommandlineResult>(false);
|
||||
}
|
||||
// If there's a valid ID returned, then let's try and find the peasant
|
||||
// that goes with it. Alternatively, if we were given a magic windowing
|
||||
// constant, we can use that to look up an appropriate peasant.
|
||||
|
@ -687,6 +695,11 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
case WindowingBehaviorUseName:
|
||||
windowID = _lookupPeasantIdForName(targetWindowName);
|
||||
break;
|
||||
case WindowingBehaviorUseNone:
|
||||
// This should be impossible. The if statement above should have
|
||||
// prevented WindowingBehaviorUseNone from falling in here.
|
||||
// Explode, because this is a programming error.
|
||||
THROW_HR(E_UNEXPECTED);
|
||||
default:
|
||||
windowID = ::base::saturated_cast<uint64_t>(targetWindow);
|
||||
break;
|
||||
|
@ -724,6 +737,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
result->WindowName(targetWindowName);
|
||||
result->ShouldCreateWindow(true);
|
||||
|
||||
_RequestNewWindowHandlers(*this, *winrt::make_self<WindowRequestedArgs>(*result, args));
|
||||
|
||||
// If this fails, it'll be logged in the following
|
||||
// TraceLoggingWrite statement, with succeeded=false
|
||||
}
|
||||
|
@ -759,6 +774,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
auto result{ winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(true) };
|
||||
result->Id(windowID);
|
||||
result->WindowName(targetWindowName);
|
||||
|
||||
_RequestNewWindowHandlers(*this, *winrt::make_self<WindowRequestedArgs>(*result, args));
|
||||
|
||||
return *result;
|
||||
}
|
||||
}
|
||||
|
@ -773,6 +791,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// In this case, no usable ID was provided. Return { true, nullopt }
|
||||
auto result = winrt::make_self<Remoting::implementation::ProposeCommandlineResult>(true);
|
||||
result->WindowName(targetWindowName);
|
||||
|
||||
_RequestNewWindowHandlers(*this, *winrt::make_self<WindowRequestedArgs>(*result, args));
|
||||
|
||||
return *result;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "Monarch.g.h"
|
||||
#include "Peasant.h"
|
||||
#include "WindowActivatedArgs.h"
|
||||
#include "WindowRequestedArgs.g.h"
|
||||
#include <atomic>
|
||||
|
||||
// We sure different GUIDs here depending on whether we're running a Release,
|
||||
|
@ -38,6 +39,26 @@ namespace RemotingUnitTests
|
|||
|
||||
namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
{
|
||||
struct WindowRequestedArgs : public WindowRequestedArgsT<WindowRequestedArgs>
|
||||
{
|
||||
public:
|
||||
WindowRequestedArgs(const Remoting::ProposeCommandlineResult& windowInfo, const Remoting::CommandlineArgs& command) :
|
||||
_Id{ windowInfo.Id() ? windowInfo.Id().Value() : 0 }, // We'll use 0 as a sentinel, since no window will ever get to have that ID
|
||||
_WindowName{ windowInfo.WindowName() },
|
||||
_args{ command.Commandline() },
|
||||
_CurrentDirectory{ command.CurrentDirectory() } {};
|
||||
|
||||
void Commandline(const winrt::array_view<const winrt::hstring>& value) { _args = { value.begin(), value.end() }; };
|
||||
winrt::com_array<winrt::hstring> Commandline() { return winrt::com_array<winrt::hstring>{ _args.begin(), _args.end() }; }
|
||||
|
||||
WINRT_PROPERTY(uint64_t, Id);
|
||||
WINRT_PROPERTY(winrt::hstring, WindowName);
|
||||
WINRT_PROPERTY(winrt::hstring, CurrentDirectory);
|
||||
|
||||
private:
|
||||
winrt::com_array<winrt::hstring> _args;
|
||||
};
|
||||
|
||||
struct Monarch : public MonarchT<Monarch>
|
||||
{
|
||||
Monarch();
|
||||
|
@ -67,6 +88,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
TYPED_EVENT(WindowClosed, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs);
|
||||
|
||||
TYPED_EVENT(RequestNewWindow, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs);
|
||||
|
||||
private:
|
||||
uint64_t _ourPID;
|
||||
|
||||
|
@ -191,4 +214,5 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
namespace winrt::Microsoft::Terminal::Remoting::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(Monarch);
|
||||
BASIC_FACTORY(WindowRequestedArgs);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,16 @@ namespace Microsoft.Terminal.Remoting
|
|||
Boolean ShouldCreateWindow { get; }; // If you name this `CreateWindow`, the compiler will explode
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass WindowRequestedArgs {
|
||||
WindowRequestedArgs(ProposeCommandlineResult windowInfo, CommandlineArgs command);
|
||||
|
||||
UInt64 Id { get; };
|
||||
String WindowName { get; };
|
||||
|
||||
String[] Commandline { get; };
|
||||
String CurrentDirectory { get; };
|
||||
}
|
||||
|
||||
[default_interface] runtimeclass SummonWindowSelectionArgs {
|
||||
SummonWindowSelectionArgs();
|
||||
SummonWindowSelectionArgs(String windowName);
|
||||
|
@ -66,6 +76,8 @@ namespace Microsoft.Terminal.Remoting
|
|||
event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> WindowClosed;
|
||||
event Windows.Foundation.TypedEventHandler<Object, QuitAllRequestedArgs> QuitAllRequested;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, WindowRequestedArgs> RequestNewWindow;
|
||||
};
|
||||
|
||||
runtimeclass Monarch : [default] IMonarch
|
||||
|
|
|
@ -43,7 +43,6 @@ namespace Microsoft.Terminal.Remoting
|
|||
ToMouse,
|
||||
};
|
||||
|
||||
|
||||
[default_interface] runtimeclass SummonWindowBehavior {
|
||||
SummonWindowBehavior();
|
||||
Boolean MoveToCurrentDesktop;
|
||||
|
|
|
@ -2,10 +2,13 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "WindowManager.h"
|
||||
#include "MonarchFactory.h"
|
||||
#include "CommandlineArgs.h"
|
||||
|
||||
#include "../inc/WindowingBehavior.h"
|
||||
#include "MonarchFactory.h"
|
||||
|
||||
#include "CommandlineArgs.h"
|
||||
#include "FindTargetWindowArgs.h"
|
||||
#include "ProposeCommandlineResult.h"
|
||||
|
||||
|
@ -21,32 +24,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
{
|
||||
WindowManager::WindowManager()
|
||||
{
|
||||
_monarchWaitInterrupt.create();
|
||||
|
||||
// Register with COM as a server for the Monarch class
|
||||
_registerAsMonarch();
|
||||
// Instantiate an instance of the Monarch. This may or may not be in-proc!
|
||||
auto foundMonarch = false;
|
||||
while (!foundMonarch)
|
||||
{
|
||||
try
|
||||
{
|
||||
_createMonarchAndCallbacks();
|
||||
// _createMonarchAndCallbacks will initialize _isKing
|
||||
foundMonarch = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If we fail to find the monarch,
|
||||
// stay in this jail until we do.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ExceptionInCtor",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WindowManager::~WindowManager()
|
||||
{
|
||||
// IMPORTANT! Tear down the registration as soon as we exit. If we're not a
|
||||
|
@ -55,32 +33,178 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// monarch!
|
||||
CoRevokeClassObject(_registrationHostClass);
|
||||
_registrationHostClass = 0;
|
||||
SignalClose();
|
||||
_monarchWaitInterrupt.SetEvent();
|
||||
|
||||
// A thread is joinable once it's been started. Basically this just
|
||||
// makes sure that the thread isn't just default-constructed.
|
||||
if (_electionThread.joinable())
|
||||
{
|
||||
_electionThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::SignalClose()
|
||||
void WindowManager::_createMonarch()
|
||||
{
|
||||
// Heads up! This only works because we're using
|
||||
// "metadata-based-marshalling" for our WinRT types. That means the OS is
|
||||
// using the .winmd file we generate to figure out the proxy/stub
|
||||
// definitions for our types automatically. This only works in the following
|
||||
// cases:
|
||||
//
|
||||
// * If we're running unpackaged: the .winmd must be a sibling of the .exe
|
||||
// * If we're running packaged: the .winmd must be in the package root
|
||||
_monarch = try_create_instance<Remoting::IMonarch>(Monarch_clsid,
|
||||
CLSCTX_LOCAL_SERVER);
|
||||
}
|
||||
|
||||
// Check if we became the king, and if we are, wire up callbacks.
|
||||
void WindowManager::_createCallbacks()
|
||||
{
|
||||
assert(_monarch);
|
||||
// Here, we're the king!
|
||||
//
|
||||
// This is where you should do any additional setup that might need to be
|
||||
// done when we become the king. This will be called both for the first
|
||||
// window, and when the current monarch dies.
|
||||
|
||||
_monarch.WindowCreated({ get_weak(), &WindowManager::_WindowCreatedHandlers });
|
||||
_monarch.WindowClosed({ get_weak(), &WindowManager::_WindowClosedHandlers });
|
||||
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
|
||||
_monarch.QuitAllRequested({ get_weak(), &WindowManager::_QuitAllRequestedHandlers });
|
||||
|
||||
_monarch.RequestNewWindow({ get_weak(), &WindowManager::_raiseRequestNewWindow });
|
||||
}
|
||||
|
||||
void WindowManager::_registerAsMonarch()
|
||||
{
|
||||
winrt::check_hresult(CoRegisterClassObject(Monarch_clsid,
|
||||
winrt::make<::MonarchFactory>().get(),
|
||||
CLSCTX_LOCAL_SERVER,
|
||||
REGCLS_MULTIPLEUSE,
|
||||
&_registrationHostClass));
|
||||
}
|
||||
|
||||
void WindowManager::_raiseFindTargetWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args)
|
||||
{
|
||||
_FindTargetWindowRequestedHandlers(sender, args);
|
||||
}
|
||||
void WindowManager::_raiseRequestNewWindow(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs& args)
|
||||
{
|
||||
_RequestNewWindowHandlers(sender, args);
|
||||
}
|
||||
|
||||
Remoting::ProposeCommandlineResult WindowManager::ProposeCommandline(const Remoting::CommandlineArgs& args, const bool isolatedMode)
|
||||
{
|
||||
if (!isolatedMode)
|
||||
{
|
||||
// _createMonarch always attempts to connect an existing monarch. In
|
||||
// isolated mode, we don't want to do that.
|
||||
_createMonarch();
|
||||
}
|
||||
|
||||
if (_monarch)
|
||||
{
|
||||
try
|
||||
// We connected to a monarch instance, not us though. This won't hit
|
||||
// in isolated mode.
|
||||
|
||||
// Send the commandline over to the monarch process
|
||||
if (_proposeToMonarch(args))
|
||||
{
|
||||
_monarch.SignalClose(_peasant.GetID());
|
||||
// If that succeeded, then we don't need to make a new window.
|
||||
// Our job is done. Either the monarch is going to run the
|
||||
// commandline in an existing window, or a new one, but either way,
|
||||
// this process doesn't need to make a new window.
|
||||
|
||||
return winrt::make<ProposeCommandlineResult>(false);
|
||||
}
|
||||
// Otherwise, we'll try to handle this ourselves.
|
||||
}
|
||||
|
||||
// Theoretically, this condition is always true here:
|
||||
//
|
||||
// if (_monarch == nullptr)
|
||||
//
|
||||
// If we do still have a _monarch at this point, then we must have
|
||||
// successfully proposed to it in _proposeToMonarch, so we can't get
|
||||
// here with a monarch.
|
||||
{
|
||||
// No preexisting instance.
|
||||
|
||||
// Raise an event, to ask how to handle this commandline. We can't ask
|
||||
// the app ourselves - we exist isolated from that knowledge (and
|
||||
// dependency hell). The WindowManager will raise this up to the app
|
||||
// host, which will then ask the AppLogic, who will then parse the
|
||||
// commandline and determine the provided ID of the window.
|
||||
auto findWindowArgs{ winrt::make_self<Remoting::implementation::FindTargetWindowArgs>(args) };
|
||||
|
||||
// This is handled by some handler in-proc
|
||||
_FindTargetWindowRequestedHandlers(*this, *findWindowArgs);
|
||||
|
||||
// After the event was handled, ResultTargetWindow() will be filled with
|
||||
// the parsed result.
|
||||
const auto targetWindow = findWindowArgs->ResultTargetWindow();
|
||||
const auto targetWindowName = findWindowArgs->ResultTargetWindowName();
|
||||
|
||||
if (targetWindow == WindowingBehaviorUseNone)
|
||||
{
|
||||
// This commandline doesn't deserve a window. Don't make a monarch
|
||||
// either.
|
||||
return winrt::make<ProposeCommandlineResult>(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This commandline _does_ want a window, which means we do want
|
||||
// to create a window, and a monarch.
|
||||
//
|
||||
// Congrats! This is now THE PROCESS. It's the only one that's
|
||||
// getting any windows.
|
||||
|
||||
// In isolated mode, we don't want to register as the monarch,
|
||||
// we just want to make a local one. So we'll skip this step.
|
||||
// The condition below it will handle making the unregistered
|
||||
// local monarch.
|
||||
|
||||
if (!isolatedMode)
|
||||
{
|
||||
_registerAsMonarch();
|
||||
_createMonarch();
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_IntentionallyIsolated",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
|
||||
if (!_monarch)
|
||||
{
|
||||
// Something catastrophically bad happened here OR we were
|
||||
// intentionally in isolated mode. We don't want to just
|
||||
// exit immediately. Instead, we'll just instantiate a local
|
||||
// Monarch instance, without registering it. We're firmly in
|
||||
// the realm of undefined behavior, but better to have some
|
||||
// window than not.
|
||||
_monarch = winrt::make<Monarch>();
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_FailedToCoCreate",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
_createCallbacks();
|
||||
|
||||
// So, we wanted a new peasant. Cool!
|
||||
//
|
||||
// We need to fill in args.ResultTargetWindow,
|
||||
// args.ResultTargetWindowName so that we can create the new
|
||||
// window with those values. Otherwise, the very first window
|
||||
// won't obey the given name / ID.
|
||||
//
|
||||
// So let's just ask the monarch (ourselves) to get those values.
|
||||
return _monarch.ProposeCommandline(args);
|
||||
}
|
||||
CATCH_LOG()
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::_proposeToMonarch(const Remoting::CommandlineArgs& args,
|
||||
std::optional<uint64_t>& givenID,
|
||||
winrt::hstring& givenName)
|
||||
// Method Description:
|
||||
// - Helper attempting to call to the monarch multiple times. If the monarch
|
||||
// fails to respond, or we encounter any sort of error, we'll try again
|
||||
// until we find one, or decisively determine there isn't one.
|
||||
bool WindowManager::_proposeToMonarch(const Remoting::CommandlineArgs& args)
|
||||
{
|
||||
// these two errors are Win32 errors, convert them to HRESULTS so we can actually compare below.
|
||||
static constexpr auto RPC_SERVER_UNAVAILABLE_HR = HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE);
|
||||
|
@ -114,10 +238,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
// dies between now and the inspection of
|
||||
// `result.ShouldCreateWindow` below, we don't want to explode
|
||||
// (since _proposeToMonarch is not try/caught).
|
||||
auto outOfProcResult = _monarch.ProposeCommandline(args);
|
||||
result = winrt::make<implementation::ProposeCommandlineResult>(outOfProcResult);
|
||||
|
||||
proposedCommandline = true;
|
||||
_monarch.ProposeCommandline(args);
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
@ -154,560 +277,75 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
_monarch = winrt::make<winrt::Microsoft::Terminal::Remoting::implementation::Monarch>();
|
||||
_createCallbacks();
|
||||
// Set the monarch to null, so that we'll create a new one
|
||||
// (or just generally check if we need to even make a window
|
||||
// for this commandline.)
|
||||
_monarch = nullptr;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// We failed to ask the monarch. It must have died. Try and
|
||||
// find the real monarch. Don't perform an election, that
|
||||
// assumes we have a peasant, which we don't yet.
|
||||
_createMonarchAndCallbacks();
|
||||
// _createMonarchAndCallbacks will initialize _isKing
|
||||
}
|
||||
if (_isKing)
|
||||
{
|
||||
// We became the king. We don't need to ProposeCommandline to ourself, we're just
|
||||
// going to do it.
|
||||
//
|
||||
// Return early, because there's nothing else for us to do here.
|
||||
// find another monarch.
|
||||
_createMonarch();
|
||||
if (!_monarch)
|
||||
{
|
||||
// We failed to create a monarch. That means there
|
||||
// aren't any other windows, and we can become the monarch.
|
||||
return false;
|
||||
}
|
||||
// Go back around the loop.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_proposeToMonarch_becameKing",
|
||||
"WindowManager_proposeToMonarch_tryAgain",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
// In WindowManager::ProposeCommandline, had we been the
|
||||
// king originally, we would have started by setting
|
||||
// this to true. We became the monarch here, so set it
|
||||
// here as well.
|
||||
_shouldCreateWindow = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Here, we created the new monarch, it wasn't us, so we're
|
||||
// gonna go through the while loop again and ask the new
|
||||
// king.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_proposeToMonarch_tryAgain",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
}
|
||||
|
||||
// Here, the monarch (not us) has replied to the message. Get the
|
||||
// valuables out of the response:
|
||||
_shouldCreateWindow = result.ShouldCreateWindow();
|
||||
if (result.Id())
|
||||
{
|
||||
givenID = result.Id().Value();
|
||||
}
|
||||
givenName = result.WindowName();
|
||||
|
||||
// TraceLogging doesn't have a good solution for logging an
|
||||
// optional. So we have to repeat the calls here:
|
||||
if (givenID)
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingUInt64(givenID.value(), "Id", "The ID we should assign our peasant"),
|
||||
TraceLoggingWideString(givenName.c_str(), "Name", "The name we should assign this window"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingPointer(nullptr, "Id", "No ID provided"),
|
||||
TraceLoggingWideString(givenName.c_str(), "Name", "The name we should assign this window"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
}
|
||||
void WindowManager::ProposeCommandline(const Remoting::CommandlineArgs& args)
|
||||
{
|
||||
// If we're the king, we _definitely_ want to process the arguments, we were
|
||||
// launched with them!
|
||||
//
|
||||
// Otherwise, the King will tell us if we should make a new window
|
||||
_shouldCreateWindow = _isKing;
|
||||
std::optional<uint64_t> givenID;
|
||||
winrt::hstring givenName{};
|
||||
if (!_isKing)
|
||||
{
|
||||
_proposeToMonarch(args, givenID, givenName);
|
||||
}
|
||||
|
||||
// During _proposeToMonarch, it's possible that we found that the king was dead, and we're the new king. Cool! Do this now.
|
||||
if (_isKing)
|
||||
{
|
||||
// We're the monarch, we don't need to propose anything. We're just
|
||||
// going to do it.
|
||||
//
|
||||
// However, we _do_ need to ask what our name should be. It's
|
||||
// possible someone started the _first_ wt with something like `wt
|
||||
// -w king` as the commandline - we want to make sure we set our
|
||||
// name to "king".
|
||||
//
|
||||
// The FindTargetWindow event is the WindowManager's way of saying
|
||||
// "I do not know how to figure out how to turn this list of args
|
||||
// into a window ID/name. Whoever's listening to this event does, so
|
||||
// I'll ask them". It's a convoluted way of hooking the
|
||||
// WindowManager up to AppLogic without actually telling it anything
|
||||
// about TerminalApp (or even WindowsTerminal)
|
||||
auto findWindowArgs{ winrt::make_self<Remoting::implementation::FindTargetWindowArgs>(args) };
|
||||
_raiseFindTargetWindowRequested(nullptr, *findWindowArgs);
|
||||
|
||||
const auto responseId = findWindowArgs->ResultTargetWindow();
|
||||
if (responseId > 0)
|
||||
{
|
||||
givenID = ::base::saturated_cast<uint64_t>(responseId);
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline_AsMonarch",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingUInt64(givenID.value(), "Id", "The ID we should assign our peasant"),
|
||||
TraceLoggingWideString(givenName.c_str(), "Name", "The name we should assign this window"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
else if (responseId == WindowingBehaviorUseName)
|
||||
{
|
||||
givenName = findWindowArgs->ResultTargetWindowName();
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline_AsMonarch",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingUInt64(0, "Id", "The ID we should assign our peasant"),
|
||||
TraceLoggingWideString(givenName.c_str(), "Name", "The name we should assign this window"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ProposeCommandline_AsMonarch",
|
||||
TraceLoggingBoolean(_shouldCreateWindow, "CreateWindow", "true iff we should create a new window"),
|
||||
TraceLoggingUInt64(0, "Id", "The ID we should assign our peasant"),
|
||||
TraceLoggingWideString(L"", "Name", "The name we should assign this window"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
}
|
||||
|
||||
if (_shouldCreateWindow)
|
||||
{
|
||||
// If we should create a new window, then instantiate our Peasant
|
||||
// instance, and tell that peasant to handle that commandline.
|
||||
_createOurPeasant({ givenID }, givenName);
|
||||
|
||||
// Spawn a thread to wait on the monarch, and handle the election
|
||||
if (!_isKing)
|
||||
{
|
||||
_createPeasantThread();
|
||||
}
|
||||
|
||||
// This right here will just tell us to stash the args away for the
|
||||
// future. The AppHost hasn't yet set up the callbacks, and the rest
|
||||
// of the app hasn't started at all. We'll note them and come back
|
||||
// later.
|
||||
_peasant.ExecuteCommandline(args);
|
||||
}
|
||||
// Otherwise, we'll do _nothing_.
|
||||
// I don't think we can ever get here, but the compiler doesn't know
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WindowManager::ShouldCreateWindow()
|
||||
{
|
||||
return _shouldCreateWindow;
|
||||
}
|
||||
|
||||
void WindowManager::_registerAsMonarch()
|
||||
{
|
||||
winrt::check_hresult(CoRegisterClassObject(Monarch_clsid,
|
||||
winrt::make<::MonarchFactory>().get(),
|
||||
CLSCTX_LOCAL_SERVER,
|
||||
REGCLS_MULTIPLEUSE,
|
||||
&_registrationHostClass));
|
||||
}
|
||||
|
||||
void WindowManager::_createMonarch()
|
||||
{
|
||||
// Heads up! This only works because we're using
|
||||
// "metadata-based-marshalling" for our WinRT types. That means the OS is
|
||||
// using the .winmd file we generate to figure out the proxy/stub
|
||||
// definitions for our types automatically. This only works in the following
|
||||
// cases:
|
||||
//
|
||||
// * If we're running unpackaged: the .winmd must be a sibling of the .exe
|
||||
// * If we're running packaged: the .winmd must be in the package root
|
||||
_monarch = create_instance<Remoting::IMonarch>(Monarch_clsid,
|
||||
CLSCTX_LOCAL_SERVER);
|
||||
}
|
||||
|
||||
// Tries to instantiate a monarch, tries again, and eventually either throws
|
||||
// (so that the caller will try again) or falls back to the isolated
|
||||
// monarch.
|
||||
void WindowManager::_redundantCreateMonarch()
|
||||
{
|
||||
_createMonarch();
|
||||
|
||||
if (_monarch == nullptr)
|
||||
{
|
||||
// See MSFT:38540483, GH#12774 for details.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_NullMonarchTryAgain",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
// Here we're gonna just give it a quick second try.Probably not
|
||||
// definitive, but might help.
|
||||
_createMonarch();
|
||||
}
|
||||
|
||||
if (_monarch == nullptr)
|
||||
{
|
||||
// See MSFT:38540483, GH#12774 for details.
|
||||
if constexpr (Feature_IsolatedMonarchMode::IsEnabled())
|
||||
{
|
||||
// Fall back to having a in proc monarch. Were now isolated from
|
||||
// other windows. This is a pretty torn state, but at least we
|
||||
// didn't just explode.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_NullMonarchIsolateMode",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
_monarch = winrt::make<winrt::Microsoft::Terminal::Remoting::implementation::Monarch>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The monarch is null. We're hoping that we can find another,
|
||||
// hopefully us. We're gonna go back around the loop again and
|
||||
// see what happens. If this is really an infinite loop (where
|
||||
// the OS won't even give us back US as the monarch), then I
|
||||
// suppose we'll find out soon enough.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_NullMonarchTryAgain",
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
winrt::hresult_error(E_UNEXPECTED, L"Did not expect the Monarch to ever be null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: This can throw! Callers include:
|
||||
// - the constructor, who performs this in a loop until it successfully
|
||||
// find a a monarch
|
||||
// - the performElection method, which is called in the waitOnMonarch
|
||||
// thread. All the calls in that thread are wrapped in try/catch's
|
||||
// already.
|
||||
// - _createOurPeasant, who might do this in a loop to establish us with the
|
||||
// monarch.
|
||||
void WindowManager::_createMonarchAndCallbacks()
|
||||
{
|
||||
_redundantCreateMonarch();
|
||||
// We're pretty confident that we have a Monarch here.
|
||||
_createCallbacks();
|
||||
}
|
||||
|
||||
// Check if we became the king, and if we are, wire up callbacks.
|
||||
void WindowManager::_createCallbacks()
|
||||
{
|
||||
// Save the result of checking if we're the king. We want to avoid
|
||||
// unnecessary calls back and forth if we can.
|
||||
_isKing = _areWeTheKing();
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ConnectedToMonarch",
|
||||
TraceLoggingUInt64(_monarch.GetPID(), "monarchPID", "The PID of the new Monarch"),
|
||||
TraceLoggingBoolean(_isKing, "isKing", "true if we are the new monarch"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
if (_peasant)
|
||||
{
|
||||
if (const auto& lastActivated{ _peasant.GetLastActivatedArgs() })
|
||||
{
|
||||
// Inform the monarch of the time we were last activated
|
||||
_monarch.HandleActivatePeasant(lastActivated);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_isKing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// Here, we're the king!
|
||||
//
|
||||
// This is where you should do any additional setup that might need to be
|
||||
// done when we become the king. This will be called both for the first
|
||||
// window, and when the current monarch dies.
|
||||
|
||||
_monarch.WindowCreated({ get_weak(), &WindowManager::_WindowCreatedHandlers });
|
||||
_monarch.WindowClosed({ get_weak(), &WindowManager::_WindowClosedHandlers });
|
||||
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
|
||||
_monarch.ShowNotificationIconRequested([this](auto&&, auto&&) { _ShowNotificationIconRequestedHandlers(*this, nullptr); });
|
||||
_monarch.HideNotificationIconRequested([this](auto&&, auto&&) { _HideNotificationIconRequestedHandlers(*this, nullptr); });
|
||||
_monarch.QuitAllRequested({ get_weak(), &WindowManager::_QuitAllRequestedHandlers });
|
||||
|
||||
_BecameMonarchHandlers(*this, nullptr);
|
||||
}
|
||||
|
||||
bool WindowManager::_areWeTheKing()
|
||||
{
|
||||
const auto ourPID{ GetCurrentProcessId() };
|
||||
const auto kingPID{ _monarch.GetPID() };
|
||||
return (ourPID == kingPID);
|
||||
}
|
||||
|
||||
Remoting::IPeasant WindowManager::_createOurPeasant(std::optional<uint64_t> givenID,
|
||||
const winrt::hstring& givenName)
|
||||
Remoting::Peasant WindowManager::CreatePeasant(const Remoting::WindowRequestedArgs& args)
|
||||
{
|
||||
auto p = winrt::make_self<Remoting::implementation::Peasant>();
|
||||
if (givenID)
|
||||
// This will be false if the Id is 0, which is our sentinel for "no specific ID was requested"
|
||||
if (const auto id = args.Id())
|
||||
{
|
||||
p->AssignID(givenID.value());
|
||||
p->AssignID(id);
|
||||
}
|
||||
|
||||
// If the name wasn't specified, this will be an empty string.
|
||||
p->WindowName(givenName);
|
||||
_peasant = *p;
|
||||
p->WindowName(args.WindowName());
|
||||
|
||||
// Try to add us to the monarch. If that fails, try to find a monarch
|
||||
// again, until we find one (we will eventually find us)
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
_monarch.AddPeasant(_peasant);
|
||||
break;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Wrap this in its own try/catch, because this can throw.
|
||||
_createMonarchAndCallbacks();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
p->ExecuteCommandline(*winrt::make_self<CommandlineArgs>(args.Commandline(), args.CurrentDirectory()));
|
||||
|
||||
_peasant.GetWindowLayoutRequested({ get_weak(), &WindowManager::_GetWindowLayoutRequestedHandlers });
|
||||
_monarch.AddPeasant(*p);
|
||||
|
||||
p->GetWindowLayoutRequested({ get_weak(), &WindowManager::_GetWindowLayoutRequestedHandlers });
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_CreateOurPeasant",
|
||||
TraceLoggingUInt64(_peasant.GetID(), "peasantID", "The ID of our new peasant"),
|
||||
TraceLoggingUInt64(p->GetID(), "peasantID", "The ID of our new peasant"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
// If the peasant asks us to quit we should not try to act in future elections.
|
||||
_peasant.QuitRequested([weakThis{ get_weak() }](auto&&, auto&&) {
|
||||
if (auto wm = weakThis.get())
|
||||
{
|
||||
wm->_monarchWaitInterrupt.SetEvent();
|
||||
}
|
||||
});
|
||||
|
||||
return _peasant;
|
||||
return *p;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempt to connect to the monarch process. This might be us!
|
||||
// - For the new monarch, add us to their list of peasants.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true iff we're the new monarch process.
|
||||
// NOTE: This can throw!
|
||||
bool WindowManager::_performElection()
|
||||
void WindowManager::SignalClose(const Remoting::Peasant& peasant)
|
||||
{
|
||||
_createMonarchAndCallbacks();
|
||||
|
||||
// Tell the new monarch who we are. We might be that monarch!
|
||||
_monarch.AddPeasant(_peasant);
|
||||
|
||||
// This method is only called when a _new_ monarch is elected. So
|
||||
// don't do anything here that needs to be done for all monarch
|
||||
// windows. This should only be for work that's done when a window
|
||||
// _becomes_ a monarch, after the death of the previous monarch.
|
||||
return _isKing;
|
||||
}
|
||||
|
||||
void WindowManager::_createPeasantThread()
|
||||
{
|
||||
// If we catch an exception trying to get at the monarch ever, we can
|
||||
// set the _monarchWaitInterrupt, and use that to trigger a new
|
||||
// election. Though, we wouldn't be able to retry the function that
|
||||
// caused the exception in the first place...
|
||||
|
||||
_electionThread = std::thread([this] {
|
||||
_waitOnMonarchThread();
|
||||
});
|
||||
}
|
||||
|
||||
void WindowManager::_waitOnMonarchThread()
|
||||
{
|
||||
// This is the array of HANDLEs that we're going to wait on in
|
||||
// WaitForMultipleObjects below.
|
||||
// * waits[0] will be the handle to the monarch process. It gets
|
||||
// signalled when the process exits / dies.
|
||||
// * waits[1] is the handle to our _monarchWaitInterrupt event. Another
|
||||
// thread can use that to manually break this loop. We'll do that when
|
||||
// we're getting torn down.
|
||||
HANDLE waits[2];
|
||||
waits[1] = _monarchWaitInterrupt.get();
|
||||
const auto peasantID = _peasant.GetID(); // safe: _peasant is in-proc.
|
||||
|
||||
auto exitThreadRequested = false;
|
||||
while (!exitThreadRequested)
|
||||
if (_monarch)
|
||||
{
|
||||
// At any point in all this, the current monarch might die. If it
|
||||
// does, we'll go straight to a new election, in the "jail"
|
||||
// try/catch below. Worst case, eventually, we'll become the new
|
||||
// monarch.
|
||||
try
|
||||
{
|
||||
// This might fail to even ask the monarch for its PID.
|
||||
wil::unique_handle hMonarch{ OpenProcess(PROCESS_ALL_ACCESS,
|
||||
FALSE,
|
||||
static_cast<DWORD>(_monarch.GetPID())) };
|
||||
|
||||
// If we fail to open the monarch, then they don't exist
|
||||
// anymore! Go straight to an election.
|
||||
if (hMonarch.get() == nullptr)
|
||||
{
|
||||
const auto gle = GetLastError();
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_FailedToOpenMonarch",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
exitThreadRequested = _performElection();
|
||||
continue;
|
||||
}
|
||||
|
||||
waits[0] = hMonarch.get();
|
||||
auto waitResult = WaitForMultipleObjects(2, waits, FALSE, INFINITE);
|
||||
|
||||
switch (waitResult)
|
||||
{
|
||||
case WAIT_OBJECT_0 + 0: // waits[0] was signaled, the handle to the monarch process
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_MonarchDied",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
// Connect to the new monarch, which might be us!
|
||||
// If we become the monarch, then we'll return true and exit this thread.
|
||||
exitThreadRequested = _performElection();
|
||||
break;
|
||||
|
||||
case WAIT_OBJECT_0 + 1: // waits[1] was signaled, our manual interrupt
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_MonarchWaitInterrupted",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
exitThreadRequested = true;
|
||||
break;
|
||||
|
||||
case WAIT_TIMEOUT:
|
||||
// This should be impossible.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_MonarchWaitTimeout",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
exitThreadRequested = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
// Returning any other value is invalid. Just die.
|
||||
const auto gle = GetLastError();
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_WaitFailed",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingUInt64(gle, "lastError", "The result of GetLastError"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
ExitProcess(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Theoretically, if window[1] dies when we're trying to get
|
||||
// its PID we'll get here. If we just try to do the election
|
||||
// once here, it's possible we might elect window[2], but have
|
||||
// it die before we add ourselves as a peasant. That
|
||||
// _performElection call will throw, and we wouldn't catch it
|
||||
// here, and we'd die.
|
||||
|
||||
// Instead, we're going to have a resilient election process.
|
||||
// We're going to keep trying an election, until one _doesn't_
|
||||
// throw an exception. That might mean burning through all the
|
||||
// other dying monarchs until we find us as the monarch. But if
|
||||
// this process is alive, then there's _someone_ in the line of
|
||||
// succession.
|
||||
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ExceptionInWaitThread",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
auto foundNewMonarch = false;
|
||||
while (!foundNewMonarch)
|
||||
{
|
||||
try
|
||||
{
|
||||
exitThreadRequested = _performElection();
|
||||
// It doesn't matter if we're the monarch, or someone
|
||||
// else is, but if we complete the election, then we've
|
||||
// registered with a new one. We can escape this jail
|
||||
// and re-enter society.
|
||||
foundNewMonarch = true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// If we fail to acknowledge the results of the election,
|
||||
// stay in this jail until we do.
|
||||
TraceLoggingWrite(g_hRemotingProvider,
|
||||
"WindowManager_ExceptionInNestedWaitThread",
|
||||
TraceLoggingUInt64(peasantID, "peasantID", "Our peasant ID"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
}
|
||||
_monarch.SignalClose(peasant.GetID());
|
||||
}
|
||||
CATCH_LOG()
|
||||
}
|
||||
}
|
||||
|
||||
Remoting::Peasant WindowManager::CurrentWindow()
|
||||
{
|
||||
return _peasant;
|
||||
}
|
||||
|
||||
void WindowManager::_raiseFindTargetWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args)
|
||||
{
|
||||
_FindTargetWindowRequestedHandlers(sender, args);
|
||||
}
|
||||
|
||||
bool WindowManager::IsMonarch()
|
||||
{
|
||||
return _isKing;
|
||||
}
|
||||
|
||||
void WindowManager::SummonWindow(const Remoting::SummonWindowSelectionArgs& args)
|
||||
{
|
||||
// We should only ever get called when we are the monarch, because only
|
||||
|
@ -741,42 +379,16 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Ask the monarch to show a notification icon.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget WindowManager::RequestShowNotificationIcon()
|
||||
{
|
||||
co_await winrt::resume_background();
|
||||
_peasant.RequestShowNotificationIcon();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Ask the monarch to hide its notification icon.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget WindowManager::RequestHideNotificationIcon()
|
||||
{
|
||||
auto strongThis{ get_strong() };
|
||||
co_await winrt::resume_background();
|
||||
_peasant.RequestHideNotificationIcon();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Ask the monarch to quit all windows.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget WindowManager::RequestQuitAll()
|
||||
winrt::fire_and_forget WindowManager::RequestQuitAll(Remoting::Peasant peasant)
|
||||
{
|
||||
auto strongThis{ get_strong() };
|
||||
co_await winrt::resume_background();
|
||||
_peasant.RequestQuitAll();
|
||||
peasant.RequestQuitAll();
|
||||
}
|
||||
|
||||
bool WindowManager::DoesQuakeWindowExist()
|
||||
|
@ -784,9 +396,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
return _monarch.DoesQuakeWindowExist();
|
||||
}
|
||||
|
||||
void WindowManager::UpdateActiveTabTitle(winrt::hstring title)
|
||||
void WindowManager::UpdateActiveTabTitle(const winrt::hstring& title, const Remoting::Peasant& peasant)
|
||||
{
|
||||
winrt::get_self<implementation::Peasant>(_peasant)->ActiveTabTitle(title);
|
||||
winrt::get_self<implementation::Peasant>(peasant)->ActiveTabTitle(title);
|
||||
}
|
||||
|
||||
Windows::Foundation::Collections::IVector<winrt::hstring> WindowManager::GetAllWindowLayouts()
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- WindowManager.h
|
||||
|
||||
Abstract:
|
||||
- The Window Manager takes care of coordinating the monarch and peasant for this
|
||||
process.
|
||||
|
@ -16,9 +14,7 @@ Abstract:
|
|||
- When the monarch needs to ask the TerminalApp about how to parse a
|
||||
commandline, it'll ask by raising an event that we'll bubble up to the
|
||||
AppHost.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "WindowManager.g.h"
|
||||
|
@ -29,65 +25,46 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
|||
{
|
||||
struct WindowManager : public WindowManagerT<WindowManager>
|
||||
{
|
||||
public:
|
||||
WindowManager();
|
||||
~WindowManager();
|
||||
winrt::Microsoft::Terminal::Remoting::ProposeCommandlineResult ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args, const bool isolatedMode);
|
||||
Remoting::Peasant CreatePeasant(const Remoting::WindowRequestedArgs& args);
|
||||
|
||||
void ProposeCommandline(const winrt::Microsoft::Terminal::Remoting::CommandlineArgs& args);
|
||||
bool ShouldCreateWindow();
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant CurrentWindow();
|
||||
bool IsMonarch();
|
||||
void SignalClose(const Remoting::Peasant& peasant);
|
||||
void SummonWindow(const Remoting::SummonWindowSelectionArgs& args);
|
||||
void SignalClose();
|
||||
|
||||
void SummonAllWindows();
|
||||
uint64_t GetNumberOfPeasants();
|
||||
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
|
||||
uint64_t GetNumberOfPeasants();
|
||||
|
||||
winrt::fire_and_forget RequestShowNotificationIcon();
|
||||
winrt::fire_and_forget RequestHideNotificationIcon();
|
||||
winrt::fire_and_forget RequestQuitAll();
|
||||
bool DoesQuakeWindowExist();
|
||||
void UpdateActiveTabTitle(winrt::hstring title);
|
||||
static winrt::fire_and_forget RequestQuitAll(Remoting::Peasant peasant);
|
||||
void UpdateActiveTabTitle(const winrt::hstring& title, const Remoting::Peasant& peasant);
|
||||
Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts();
|
||||
bool DoesQuakeWindowExist();
|
||||
|
||||
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
|
||||
TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
|
||||
TYPED_EVENT(WindowCreated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(WindowClosed, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(HideNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs);
|
||||
TYPED_EVENT(GetWindowLayoutRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::GetWindowLayoutArgs);
|
||||
|
||||
TYPED_EVENT(RequestNewWindow, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs);
|
||||
|
||||
private:
|
||||
bool _shouldCreateWindow{ false };
|
||||
bool _isKing{ false };
|
||||
DWORD _registrationHostClass{ 0 };
|
||||
winrt::Microsoft::Terminal::Remoting::IMonarch _monarch{ nullptr };
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant _peasant{ nullptr };
|
||||
|
||||
wil::unique_event _monarchWaitInterrupt;
|
||||
std::thread _electionThread;
|
||||
|
||||
void _registerAsMonarch();
|
||||
void _createMonarch();
|
||||
void _redundantCreateMonarch();
|
||||
void _createMonarchAndCallbacks();
|
||||
void _createCallbacks();
|
||||
bool _areWeTheKing();
|
||||
winrt::Microsoft::Terminal::Remoting::IPeasant _createOurPeasant(std::optional<uint64_t> givenID,
|
||||
const winrt::hstring& givenName);
|
||||
void _registerAsMonarch();
|
||||
|
||||
bool _performElection();
|
||||
void _createPeasantThread();
|
||||
void _waitOnMonarchThread();
|
||||
bool _proposeToMonarch(const Remoting::CommandlineArgs& args);
|
||||
|
||||
void _createCallbacks();
|
||||
void _raiseFindTargetWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args);
|
||||
|
||||
void _proposeToMonarch(const Remoting::CommandlineArgs& args,
|
||||
std::optional<uint64_t>& givenID,
|
||||
winrt::hstring& givenName);
|
||||
void _raiseRequestNewWindow(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs& args);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -7,29 +7,33 @@ namespace Microsoft.Terminal.Remoting
|
|||
[default_interface] runtimeclass WindowManager
|
||||
{
|
||||
WindowManager();
|
||||
void ProposeCommandline(CommandlineArgs args);
|
||||
void SignalClose();
|
||||
Boolean ShouldCreateWindow { get; };
|
||||
IPeasant CurrentWindow();
|
||||
Boolean IsMonarch { get; };
|
||||
|
||||
ProposeCommandlineResult ProposeCommandline(CommandlineArgs args, Boolean isolatedMode);
|
||||
Peasant CreatePeasant(WindowRequestedArgs args);
|
||||
|
||||
void SignalClose(Peasant p);
|
||||
|
||||
void UpdateActiveTabTitle(String title, Peasant p);
|
||||
static void RequestQuitAll(Peasant p);
|
||||
|
||||
void SummonWindow(SummonWindowSelectionArgs args);
|
||||
void SummonAllWindows();
|
||||
void RequestShowNotificationIcon();
|
||||
void RequestHideNotificationIcon();
|
||||
|
||||
Windows.Foundation.Collections.IVector<String> GetAllWindowLayouts();
|
||||
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos();
|
||||
|
||||
UInt64 GetNumberOfPeasants();
|
||||
void RequestQuitAll();
|
||||
void UpdateActiveTabTitle(String title);
|
||||
|
||||
Boolean DoesQuakeWindowExist();
|
||||
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> BecameMonarch;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> WindowClosed;
|
||||
event Windows.Foundation.TypedEventHandler<Object, QuitAllRequestedArgs> QuitAllRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, GetWindowLayoutArgs> GetWindowLayoutRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
|
||||
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, WindowRequestedArgs> RequestNewWindow;
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "../inc/WindowingBehavior.h"
|
||||
#include "AppLogic.g.cpp"
|
||||
#include "FindTargetWindowResult.g.cpp"
|
||||
#include "SettingsLoadEventArgs.h"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
#include <WtExeUtils.h>
|
||||
|
@ -594,7 +595,7 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
if (!appArgs.GetExitMessage().empty())
|
||||
{
|
||||
return winrt::make<FindTargetWindowResult>(WindowingBehaviorUseNew);
|
||||
return winrt::make<FindTargetWindowResult>(WindowingBehaviorUseNone);
|
||||
}
|
||||
|
||||
const std::string parsedTarget{ appArgs.GetTargetWindow() };
|
||||
|
@ -662,18 +663,14 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
}
|
||||
|
||||
// Any unsuccessful parse will be a new window. That new window will try
|
||||
// to handle the commandline itself, and find that the commandline
|
||||
// failed to parse. When that happens, the new window will display the
|
||||
// message box.
|
||||
// Any unsuccessful parse will result in _no_ window. We will indicate
|
||||
// to the caller that they shouldn't make a window. They can still find
|
||||
// the commandline failed to parse and choose to display the message
|
||||
// box.
|
||||
//
|
||||
// This will also work for the case where the user specifies an invalid
|
||||
// commandline in conjunction with `-w 0`. This function will determine
|
||||
// that the commandline has a parse error, and indicate that we should
|
||||
// create a new window. Then, in that new window, we'll try to set the
|
||||
// StartupActions, which will again fail, returning the correct error
|
||||
// message.
|
||||
return winrt::make<FindTargetWindowResult>(WindowingBehaviorUseNew);
|
||||
// commandline in conjunction with `-w 0`.
|
||||
return winrt::make<FindTargetWindowResult>(WindowingBehaviorUseNone);
|
||||
}
|
||||
|
||||
Windows::Foundation::Collections::IMapView<Microsoft::Terminal::Control::KeyChord, Microsoft::Terminal::Settings::Model::Command> AppLogic::GlobalHotkeys()
|
||||
|
@ -686,6 +683,26 @@ namespace winrt::TerminalApp::implementation
|
|||
return _settings.GlobalSettings().CurrentTheme();
|
||||
}
|
||||
|
||||
bool AppLogic::IsolatedMode()
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
ReloadSettings();
|
||||
}
|
||||
return _settings.GlobalSettings().IsolatedMode();
|
||||
}
|
||||
bool AppLogic::RequestsTrayIcon()
|
||||
{
|
||||
if (!_loadedInitialSettings)
|
||||
{
|
||||
// Load settings if we haven't already
|
||||
ReloadSettings();
|
||||
}
|
||||
const auto& globals{ _settings.GlobalSettings() };
|
||||
return globals.AlwaysShowNotificationIcon() ||
|
||||
globals.MinimizeToNotificationArea();
|
||||
}
|
||||
|
||||
TerminalApp::TerminalWindow AppLogic::CreateNewWindow()
|
||||
{
|
||||
if (_settings == nullptr)
|
||||
|
@ -734,4 +751,12 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
ApplicationState::SharedInstance().PersistedWindowLayouts(winrt::single_threaded_vector(std::move(converted)));
|
||||
}
|
||||
|
||||
TerminalApp::ParseCommandlineResult AppLogic::GetParseCommandlineMessage(array_view<const winrt::hstring> args)
|
||||
{
|
||||
::TerminalApp::AppCommandlineArgs _appArgs;
|
||||
const auto r = _appArgs.ParseArgs(args);
|
||||
return TerminalApp::ParseCommandlineResult{ winrt::to_hstring(_appArgs.GetExitMessage()), r };
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -65,9 +65,13 @@ namespace winrt::TerminalApp::implementation
|
|||
Windows::Foundation::Collections::IMapView<Microsoft::Terminal::Control::KeyChord, Microsoft::Terminal::Settings::Model::Command> GlobalHotkeys();
|
||||
|
||||
Microsoft::Terminal::Settings::Model::Theme Theme();
|
||||
bool IsolatedMode();
|
||||
bool RequestsTrayIcon();
|
||||
|
||||
TerminalApp::TerminalWindow CreateNewWindow();
|
||||
|
||||
TerminalApp::ParseCommandlineResult GetParseCommandlineMessage(array_view<const winrt::hstring> args);
|
||||
|
||||
TYPED_EVENT(SettingsChanged, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::SettingsLoadEventArgs);
|
||||
|
||||
private:
|
||||
|
|
|
@ -10,6 +10,12 @@ namespace TerminalApp
|
|||
String WindowName { get; };
|
||||
};
|
||||
|
||||
struct ParseCommandlineResult
|
||||
{
|
||||
String Message;
|
||||
Int32 ExitCode;
|
||||
};
|
||||
|
||||
// See IDialogPresenter and TerminalPage's DialogPresenter for more
|
||||
// information.
|
||||
[default_interface] runtimeclass AppLogic
|
||||
|
@ -35,13 +41,18 @@ namespace TerminalApp
|
|||
|
||||
void ReloadSettings();
|
||||
|
||||
// Selected settings to expose
|
||||
Microsoft.Terminal.Settings.Model.Theme Theme { get; };
|
||||
Boolean IsolatedMode { get; };
|
||||
Boolean RequestsTrayIcon { get; };
|
||||
|
||||
FindTargetWindowResult FindTargetWindow(String[] args);
|
||||
|
||||
TerminalWindow CreateNewWindow();
|
||||
|
||||
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Microsoft.Terminal.Settings.Model.Command> GlobalHotkeys();
|
||||
ParseCommandlineResult GetParseCommandlineMessage(String[] args);
|
||||
|
||||
IMapView<Microsoft.Terminal.Control.KeyChord, Microsoft.Terminal.Settings.Model.Command> GlobalHotkeys();
|
||||
|
||||
event Windows.Foundation.TypedEventHandler<Object, SettingsLoadEventArgs> SettingsChanged;
|
||||
|
||||
|
|
|
@ -33,9 +33,6 @@ static const int CombinedPaneBorderSize = 2 * PaneBorderSize;
|
|||
static const int AnimationDurationInMilliseconds = 200;
|
||||
static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Windows::Foundation::TimeSpan(std::chrono::milliseconds(AnimationDurationInMilliseconds)));
|
||||
|
||||
winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_focusedBorderBrush = { nullptr };
|
||||
winrt::Windows::UI::Xaml::Media::SolidColorBrush Pane::s_unfocusedBorderBrush = { nullptr };
|
||||
|
||||
Pane::Pane(const Profile& profile, const TermControl& control, const bool lastFocused) :
|
||||
_control{ control },
|
||||
_lastActive{ lastFocused },
|
||||
|
@ -83,7 +80,7 @@ Pane::Pane(std::shared_ptr<Pane> first,
|
|||
|
||||
// Use the unfocused border color as the pane background, so an actual color
|
||||
// appears behind panes as we animate them sliding in.
|
||||
_root.Background(s_unfocusedBorderBrush);
|
||||
_root.Background(_themeResources.unfocusedBorderBrush);
|
||||
|
||||
_root.Children().Append(_borderFirst);
|
||||
_root.Children().Append(_borderSecond);
|
||||
|
@ -1396,8 +1393,8 @@ void Pane::UpdateVisuals()
|
|||
{
|
||||
_UpdateBorders();
|
||||
}
|
||||
_borderFirst.BorderBrush(_lastActive ? s_focusedBorderBrush : s_unfocusedBorderBrush);
|
||||
_borderSecond.BorderBrush(_lastActive ? s_focusedBorderBrush : s_unfocusedBorderBrush);
|
||||
_borderFirst.BorderBrush(_lastActive ? _themeResources.focusedBorderBrush : _themeResources.unfocusedBorderBrush);
|
||||
_borderSecond.BorderBrush(_lastActive ? _themeResources.focusedBorderBrush : _themeResources.unfocusedBorderBrush);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -1849,7 +1846,7 @@ winrt::fire_and_forget Pane::_CloseChildRoutine(const bool closeFirst)
|
|||
Controls::Grid dummyGrid;
|
||||
// GH#603 - we can safely add a BG here, as the control is gone right
|
||||
// away, to fill the space as the rest of the pane expands.
|
||||
dummyGrid.Background(s_unfocusedBorderBrush);
|
||||
dummyGrid.Background(_themeResources.unfocusedBorderBrush);
|
||||
// It should be the size of the closed pane.
|
||||
dummyGrid.Width(removedOriginalSize.Width);
|
||||
dummyGrid.Height(removedOriginalSize.Height);
|
||||
|
@ -2127,7 +2124,7 @@ void Pane::_SetupEntranceAnimation()
|
|||
// * If we give the parent (us) root BG a color, then a transparent pane
|
||||
// will flash opaque during the animation, then back to transparent, which
|
||||
// looks bad.
|
||||
_secondChild->_root.Background(s_unfocusedBorderBrush);
|
||||
_secondChild->_root.Background(_themeResources.unfocusedBorderBrush);
|
||||
|
||||
const auto [firstSize, secondSize] = _CalcChildrenSizes(::base::saturated_cast<float>(totalSize));
|
||||
|
||||
|
@ -3092,51 +3089,20 @@ float Pane::_ClampSplitPosition(const bool widthOrHeight, const float requestedV
|
|||
return std::clamp(requestedValue, minSplitPosition, maxSplitPosition);
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Attempts to load some XAML resources that the Pane will need. This includes:
|
||||
// * The Color we'll use for active Panes's borders - SystemAccentColor
|
||||
// * The Brush we'll use for inactive Panes - TabViewBackground (to match the
|
||||
// color of the titlebar)
|
||||
// Arguments:
|
||||
// - requestedTheme: this should be the currently active Theme for the app
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Pane::SetupResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme)
|
||||
// Method Description:
|
||||
// - Update our stored brushes for the current theme. This will also recursively
|
||||
// update all our children.
|
||||
// - TerminalPage creates these brushes and ultimately owns them. Effectively,
|
||||
// we're just storing a smart pointer to the page's brushes.
|
||||
void Pane::UpdateResources(const PaneResources& resources)
|
||||
{
|
||||
const auto res = Application::Current().Resources();
|
||||
const auto accentColorKey = winrt::box_value(L"SystemAccentColor");
|
||||
if (res.HasKey(accentColorKey))
|
||||
{
|
||||
const auto colorFromResources = ThemeLookup(res, requestedTheme, accentColorKey);
|
||||
// If SystemAccentColor is _not_ a Color for some reason, use
|
||||
// Transparent as the color, so we don't do this process again on
|
||||
// the next pane (by leaving s_focusedBorderBrush nullptr)
|
||||
auto actualColor = winrt::unbox_value_or<Color>(colorFromResources, Colors::Black());
|
||||
s_focusedBorderBrush = SolidColorBrush(actualColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// DON'T use Transparent here - if it's "Transparent", then it won't
|
||||
// be able to hittest for clicks, and then clicking on the border
|
||||
// will eat focus.
|
||||
s_focusedBorderBrush = SolidColorBrush{ Colors::Black() };
|
||||
}
|
||||
_themeResources = resources;
|
||||
UpdateVisuals();
|
||||
|
||||
const auto unfocusedBorderBrushKey = winrt::box_value(L"UnfocusedBorderBrush");
|
||||
if (res.HasKey(unfocusedBorderBrushKey))
|
||||
if (!_IsLeaf())
|
||||
{
|
||||
// MAKE SURE TO USE ThemeLookup, so that we get the correct resource for
|
||||
// the requestedTheme, not just the value from the resources (which
|
||||
// might not respect the settings' requested theme)
|
||||
auto obj = ThemeLookup(res, requestedTheme, unfocusedBorderBrushKey);
|
||||
s_unfocusedBorderBrush = obj.try_as<winrt::Windows::UI::Xaml::Media::SolidColorBrush>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// DON'T use Transparent here - if it's "Transparent", then it won't
|
||||
// be able to hittest for clicks, and then clicking on the border
|
||||
// will eat focus.
|
||||
s_unfocusedBorderBrush = SolidColorBrush{ Colors::Black() };
|
||||
_firstChild->UpdateResources(resources);
|
||||
_secondChild->UpdateResources(resources);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,12 @@ enum class SplitState : int
|
|||
Vertical = 2
|
||||
};
|
||||
|
||||
struct PaneResources
|
||||
{
|
||||
winrt::Windows::UI::Xaml::Media::SolidColorBrush focusedBorderBrush{ nullptr };
|
||||
winrt::Windows::UI::Xaml::Media::SolidColorBrush unfocusedBorderBrush{ nullptr };
|
||||
};
|
||||
|
||||
class Pane : public std::enable_shared_from_this<Pane>
|
||||
{
|
||||
public:
|
||||
|
@ -136,7 +142,7 @@ public:
|
|||
|
||||
bool ContainsReadOnly() const;
|
||||
|
||||
static void SetupResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
|
||||
void UpdateResources(const PaneResources& resources);
|
||||
|
||||
// Method Description:
|
||||
// - A helper method for ad-hoc recursion on a pane tree. Walks the pane
|
||||
|
@ -217,8 +223,8 @@ private:
|
|||
winrt::Windows::UI::Xaml::Controls::Grid _root{};
|
||||
winrt::Windows::UI::Xaml::Controls::Border _borderFirst{};
|
||||
winrt::Windows::UI::Xaml::Controls::Border _borderSecond{};
|
||||
static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush;
|
||||
static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_unfocusedBorderBrush;
|
||||
|
||||
PaneResources _themeResources;
|
||||
|
||||
#pragma region Properties that need to be transferred between child / parent panes upon splitting / closing
|
||||
std::shared_ptr<Pane> _firstChild{ nullptr };
|
||||
|
|
|
@ -31,10 +31,12 @@ using namespace winrt::Windows::ApplicationModel::DataTransfer;
|
|||
using namespace winrt::Windows::Foundation::Collections;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Windows::UI;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::UI::Text;
|
||||
using namespace winrt::Windows::UI::Xaml::Controls;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::UI::Xaml::Media;
|
||||
using namespace ::TerminalApp;
|
||||
using namespace ::Microsoft::Console;
|
||||
using namespace ::Microsoft::Terminal::Core;
|
||||
|
@ -101,38 +103,11 @@ namespace winrt::TerminalApp::implementation
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Recursively check our commands to see if there's a keybinding for
|
||||
// exactly their action. If there is, label that command with the text
|
||||
// corresponding to that key chord.
|
||||
// - Will recurse into nested commands as well.
|
||||
// Arguments:
|
||||
// - settings: The settings who's keybindings we should use to look up the key chords from
|
||||
// - commands: The list of commands to label.
|
||||
static void _recursiveUpdateCommandKeybindingLabels(CascadiaSettings settings,
|
||||
IMapView<winrt::hstring, Command> commands)
|
||||
{
|
||||
for (const auto& nameAndCmd : commands)
|
||||
{
|
||||
const auto& command = nameAndCmd.Value();
|
||||
if (command.HasNestedCommands())
|
||||
{
|
||||
_recursiveUpdateCommandKeybindingLabels(settings, command.NestedCommands());
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there's a keybinding that's bound to exactly this command,
|
||||
// then get the keychord and display it as a
|
||||
// part of the command in the UI.
|
||||
// We specifically need to do this for nested commands.
|
||||
const auto keyChord{ settings.ActionMap().GetKeyBindingForAction(command.ActionAndArgs().Action(), command.ActionAndArgs().Args()) };
|
||||
command.RegisterKey(keyChord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// INVARIANT: This needs to be called on OUR UI thread!
|
||||
void TerminalPage::SetSettings(CascadiaSettings settings, bool needRefreshUI)
|
||||
{
|
||||
assert(Dispatcher().HasThreadAccess());
|
||||
|
||||
_settings = settings;
|
||||
|
||||
// Make sure to _UpdateCommandsForPalette before
|
||||
|
@ -252,9 +227,6 @@ namespace winrt::TerminalApp::implementation
|
|||
// Hookup our event handlers to the ShortcutActionDispatch
|
||||
_RegisterActionCallbacks();
|
||||
|
||||
// Hook up inbound connection event handler
|
||||
ConptyConnection::NewConnection({ this, &TerminalPage::_OnNewConnection });
|
||||
|
||||
//Event Bindings (Early)
|
||||
_newTabButton.Click([weakThis{ get_weak() }](auto&&, auto&&) {
|
||||
if (auto page{ weakThis.get() })
|
||||
|
@ -543,6 +515,9 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
_shouldStartInboundListener = false;
|
||||
|
||||
// Hook up inbound connection event handler
|
||||
_newConnectionRevoker = ConptyConnection::NewConnection(winrt::auto_revoke, { this, &TerminalPage::_OnNewConnection });
|
||||
|
||||
try
|
||||
{
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::StartInboundListener();
|
||||
|
@ -2945,50 +2920,6 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
}
|
||||
|
||||
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
|
||||
static bool _compareSchemeNames(const ColorScheme& lhs, const ColorScheme& rhs)
|
||||
{
|
||||
std::wstring leftName{ lhs.Name() };
|
||||
std::wstring rightName{ rhs.Name() };
|
||||
return leftName.compare(rightName) < 0;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Takes a mapping of names->commands and expands them
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
IMap<winrt::hstring, Command> TerminalPage::_ExpandCommands(IMapView<winrt::hstring, Command> commandsToExpand,
|
||||
IVectorView<Profile> profiles,
|
||||
IMapView<winrt::hstring, ColorScheme> schemes)
|
||||
{
|
||||
auto warnings{ winrt::single_threaded_vector<SettingsLoadWarnings>() };
|
||||
|
||||
std::vector<ColorScheme> sortedSchemes;
|
||||
sortedSchemes.reserve(schemes.Size());
|
||||
|
||||
for (const auto& nameAndScheme : schemes)
|
||||
{
|
||||
sortedSchemes.push_back(nameAndScheme.Value());
|
||||
}
|
||||
std::sort(sortedSchemes.begin(),
|
||||
sortedSchemes.end(),
|
||||
_compareSchemeNames);
|
||||
|
||||
auto copyOfCommands = winrt::single_threaded_map<winrt::hstring, Command>();
|
||||
for (const auto& nameAndCommand : commandsToExpand)
|
||||
{
|
||||
copyOfCommands.Insert(nameAndCommand.Key(), nameAndCommand.Value());
|
||||
}
|
||||
|
||||
Command::ExpandCommands(copyOfCommands,
|
||||
profiles,
|
||||
{ sortedSchemes },
|
||||
warnings);
|
||||
|
||||
return copyOfCommands;
|
||||
}
|
||||
// Method Description:
|
||||
// - Repopulates the list of commands in the command palette with the
|
||||
// current commands in the settings. Also updates the keybinding labels to
|
||||
|
@ -2999,20 +2930,9 @@ namespace winrt::TerminalApp::implementation
|
|||
// - <none>
|
||||
void TerminalPage::_UpdateCommandsForPalette()
|
||||
{
|
||||
auto copyOfCommands = _ExpandCommands(_settings.GlobalSettings().ActionMap().NameMap(),
|
||||
_settings.ActiveProfiles().GetView(),
|
||||
_settings.GlobalSettings().ColorSchemes());
|
||||
|
||||
_recursiveUpdateCommandKeybindingLabels(_settings, copyOfCommands.GetView());
|
||||
|
||||
// Update the command palette when settings reload
|
||||
auto commandsCollection = winrt::single_threaded_vector<Command>();
|
||||
for (const auto& nameAndCommand : copyOfCommands)
|
||||
{
|
||||
commandsCollection.Append(nameAndCommand.Value());
|
||||
}
|
||||
|
||||
CommandPalette().SetCommands(commandsCollection);
|
||||
const auto& expanded{ _settings.GlobalSettings().ActionMap().ExpandedCommands() };
|
||||
CommandPalette().SetCommands(expanded);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -3423,6 +3343,8 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
HRESULT TerminalPage::_OnNewConnection(const ConptyConnection& connection)
|
||||
{
|
||||
_newConnectionRevoker.revoke();
|
||||
|
||||
// We need to be on the UI thread in order for _OpenNewTab to run successfully.
|
||||
// HasThreadAccess will return true if we're currently on a UI thread and false otherwise.
|
||||
// When we're on a COM thread, we'll need to dispatch the calls to the UI thread
|
||||
|
@ -4197,17 +4119,14 @@ namespace winrt::TerminalApp::implementation
|
|||
auto requestedTheme{ theme.RequestedTheme() };
|
||||
|
||||
{
|
||||
// Update the brushes that Pane's use...
|
||||
Pane::SetupResources(requestedTheme);
|
||||
// ... then trigger a visual update for all the pane borders to
|
||||
// apply the new ones.
|
||||
_updatePaneResources(requestedTheme);
|
||||
|
||||
for (const auto& tab : _tabs)
|
||||
{
|
||||
if (auto terminalTab{ _GetTerminalTabImpl(tab) })
|
||||
{
|
||||
terminalTab->GetRootPane()->WalkTree([&](auto&& pane) {
|
||||
pane->UpdateVisuals();
|
||||
});
|
||||
// The root pane will propagate the theme change to all its children.
|
||||
terminalTab->GetRootPane()->UpdateResources(_paneResources);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4314,6 +4233,54 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Attempts to load some XAML resources that Panes will need. This includes:
|
||||
// * The Color they'll use for active Panes's borders - SystemAccentColor
|
||||
// * The Brush they'll use for inactive Panes - TabViewBackground (to match the
|
||||
// color of the titlebar)
|
||||
// Arguments:
|
||||
// - requestedTheme: this should be the currently active Theme for the app
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void TerminalPage::_updatePaneResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme)
|
||||
{
|
||||
const auto res = Application::Current().Resources();
|
||||
const auto accentColorKey = winrt::box_value(L"SystemAccentColor");
|
||||
if (res.HasKey(accentColorKey))
|
||||
{
|
||||
const auto colorFromResources = ThemeLookup(res, requestedTheme, accentColorKey);
|
||||
// If SystemAccentColor is _not_ a Color for some reason, use
|
||||
// Transparent as the color, so we don't do this process again on
|
||||
// the next pane (by leaving s_focusedBorderBrush nullptr)
|
||||
auto actualColor = winrt::unbox_value_or<Color>(colorFromResources, Colors::Black());
|
||||
_paneResources.focusedBorderBrush = SolidColorBrush(actualColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
// DON'T use Transparent here - if it's "Transparent", then it won't
|
||||
// be able to hittest for clicks, and then clicking on the border
|
||||
// will eat focus.
|
||||
_paneResources.focusedBorderBrush = SolidColorBrush{ Colors::Black() };
|
||||
}
|
||||
|
||||
const auto unfocusedBorderBrushKey = winrt::box_value(L"UnfocusedBorderBrush");
|
||||
if (res.HasKey(unfocusedBorderBrushKey))
|
||||
{
|
||||
// MAKE SURE TO USE ThemeLookup, so that we get the correct resource for
|
||||
// the requestedTheme, not just the value from the resources (which
|
||||
// might not respect the settings' requested theme)
|
||||
auto obj = ThemeLookup(res, requestedTheme, unfocusedBorderBrushKey);
|
||||
_paneResources.unfocusedBorderBrush = obj.try_as<winrt::Windows::UI::Xaml::Media::SolidColorBrush>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// DON'T use Transparent here - if it's "Transparent", then it won't
|
||||
// be able to hittest for clicks, and then clicking on the border
|
||||
// will eat focus.
|
||||
_paneResources.unfocusedBorderBrush = SolidColorBrush{ Colors::Black() };
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::WindowActivated(const bool activated)
|
||||
{
|
||||
// Stash if we're activated. Use that when we reload
|
||||
|
|
|
@ -225,6 +225,10 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
TerminalApp::WindowProperties _WindowProperties{ nullptr };
|
||||
|
||||
PaneResources _paneResources;
|
||||
|
||||
winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::NewConnection_revoker _newConnectionRevoker;
|
||||
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowDialogHelper(const std::wstring_view& name);
|
||||
|
||||
void _ShowAboutDialog();
|
||||
|
@ -268,10 +272,6 @@ namespace winrt::TerminalApp::implementation
|
|||
void _UpdateCommandsForPalette();
|
||||
void _SetBackgroundImage(const winrt::Microsoft::Terminal::Settings::Model::IAppearanceConfig& newAppearance);
|
||||
|
||||
static winrt::Windows::Foundation::Collections::IMap<winrt::hstring, Microsoft::Terminal::Settings::Model::Command> _ExpandCommands(Windows::Foundation::Collections::IMapView<winrt::hstring, Microsoft::Terminal::Settings::Model::Command> commandsToExpand,
|
||||
Windows::Foundation::Collections::IVectorView<Microsoft::Terminal::Settings::Model::Profile> profiles,
|
||||
Windows::Foundation::Collections::IMapView<winrt::hstring, Microsoft::Terminal::Settings::Model::ColorScheme> schemes);
|
||||
|
||||
void _DuplicateFocusedTab();
|
||||
void _DuplicateTab(const TerminalTab& tab);
|
||||
|
||||
|
@ -454,6 +454,7 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
void _updateThemeColors();
|
||||
void _updateTabCloseButton(const winrt::Microsoft::UI::Xaml::Controls::TabViewItem& tabViewItem);
|
||||
void _updatePaneResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
|
||||
|
||||
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
|
||||
winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
|
||||
|
|
|
@ -123,11 +123,8 @@ namespace winrt::TerminalApp::implementation
|
|||
_initialLoadResult{ settingsLoadedResult },
|
||||
_WindowProperties{ winrt::make_self<TerminalApp::implementation::WindowProperties>() }
|
||||
{
|
||||
// The TerminalPage has to be constructed during our construction, to
|
||||
// make sure that there's a terminal page for callers of
|
||||
// SetTitleBarContent
|
||||
_root = winrt::make_self<TerminalPage>(*_WindowProperties);
|
||||
_dialog = ContentDialog{};
|
||||
// The TerminalPage has to ABSOLUTELY NOT BE constructed during our
|
||||
// construction. We can't do ANY xaml till Initialize() is called.
|
||||
|
||||
// For your own sanity, it's better to do setup outside the ctor.
|
||||
// If you do any setup in the ctor that ends up throwing an exception,
|
||||
|
@ -140,6 +137,10 @@ namespace winrt::TerminalApp::implementation
|
|||
// - Implements the IInitializeWithWindow interface from shobjidl_core.
|
||||
HRESULT TerminalWindow::Initialize(HWND hwnd)
|
||||
{
|
||||
// Now that we know we can do XAML, build our page.
|
||||
_root = winrt::make_self<TerminalPage>(*_WindowProperties);
|
||||
_dialog = ContentDialog{};
|
||||
|
||||
// Pass commandline args into the TerminalPage. If we were supposed to
|
||||
// load from a persisted layout, do that instead.
|
||||
|
||||
|
@ -222,8 +223,9 @@ namespace winrt::TerminalApp::implementation
|
|||
_root->SetStartupActions(_settingsStartupArgs);
|
||||
}
|
||||
|
||||
_root->SetSettings(_settings, false);
|
||||
_root->Loaded({ this, &TerminalWindow::_OnLoaded });
|
||||
_root->SetSettings(_settings, false); // We're on our UI thread right now, so this is safe
|
||||
_root->Loaded({ get_weak(), &TerminalWindow::_OnLoaded });
|
||||
|
||||
_root->Initialized([this](auto&&, auto&&) {
|
||||
// GH#288 - When we finish initialization, if the user wanted us
|
||||
// launched _fullscreen_, toggle fullscreen mode. This will make sure
|
||||
|
@ -715,32 +717,40 @@ namespace winrt::TerminalApp::implementation
|
|||
_RequestedThemeChangedHandlers(*this, Theme());
|
||||
}
|
||||
|
||||
// This may be called on a background thread, or the main thread, but almost
|
||||
// definitely not on OUR UI thread.
|
||||
winrt::fire_and_forget TerminalWindow::UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args)
|
||||
{
|
||||
_settings = args.NewSettings();
|
||||
// Update the settings in TerminalPage
|
||||
_root->SetSettings(_settings, true);
|
||||
|
||||
const auto weakThis{ get_weak() };
|
||||
co_await wil::resume_foreground(_root->Dispatcher());
|
||||
|
||||
// Bubble the notification up to the AppHost, now that we've updated our _settings.
|
||||
_SettingsChangedHandlers(*this, args);
|
||||
|
||||
if (FAILED(args.Result()))
|
||||
// Back on our UI thread...
|
||||
if (auto logic{ weakThis.get() })
|
||||
{
|
||||
const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle");
|
||||
const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText");
|
||||
_ShowLoadErrorsDialog(titleKey,
|
||||
textKey,
|
||||
gsl::narrow_cast<HRESULT>(args.Result()),
|
||||
args.ExceptionText());
|
||||
co_return;
|
||||
// Update the settings in TerminalPage
|
||||
// We're on our UI thread right now, so this is safe
|
||||
_root->SetSettings(_settings, true);
|
||||
|
||||
// Bubble the notification up to the AppHost, now that we've updated our _settings.
|
||||
_SettingsChangedHandlers(*this, args);
|
||||
|
||||
if (FAILED(args.Result()))
|
||||
{
|
||||
const winrt::hstring titleKey = USES_RESOURCE(L"ReloadJsonParseErrorTitle");
|
||||
const winrt::hstring textKey = USES_RESOURCE(L"ReloadJsonParseErrorText");
|
||||
_ShowLoadErrorsDialog(titleKey,
|
||||
textKey,
|
||||
gsl::narrow_cast<HRESULT>(args.Result()),
|
||||
args.ExceptionText());
|
||||
co_return;
|
||||
}
|
||||
else if (args.Result() == S_FALSE)
|
||||
{
|
||||
_ShowLoadWarningsDialog(args.Warnings());
|
||||
}
|
||||
_RefreshThemeRoutine();
|
||||
}
|
||||
else if (args.Result() == S_FALSE)
|
||||
{
|
||||
_ShowLoadWarningsDialog(args.Warnings());
|
||||
}
|
||||
_RefreshThemeRoutine();
|
||||
}
|
||||
|
||||
void TerminalWindow::_OpenSettingsUI()
|
||||
|
@ -1107,6 +1117,22 @@ namespace winrt::TerminalApp::implementation
|
|||
return *_cachedLayout;
|
||||
}
|
||||
|
||||
void TerminalWindow::RequestExitFullscreen()
|
||||
{
|
||||
_root->SetFullscreen(false);
|
||||
}
|
||||
|
||||
bool TerminalWindow::AutoHideWindow()
|
||||
{
|
||||
return _settings.GlobalSettings().AutoHideWindow();
|
||||
}
|
||||
|
||||
void TerminalWindow::UpdateSettingsHandler(const winrt::IInspectable& /*sender*/,
|
||||
const winrt::TerminalApp::SettingsLoadEventArgs& args)
|
||||
{
|
||||
UpdateSettings(args);
|
||||
}
|
||||
|
||||
void TerminalWindow::IdentifyWindow()
|
||||
{
|
||||
if (_root)
|
||||
|
@ -1142,22 +1168,6 @@ namespace winrt::TerminalApp::implementation
|
|||
_WindowProperties->WindowId(id);
|
||||
}
|
||||
|
||||
void TerminalWindow::RequestExitFullscreen()
|
||||
{
|
||||
_root->SetFullscreen(false);
|
||||
}
|
||||
|
||||
bool TerminalWindow::AutoHideWindow()
|
||||
{
|
||||
return _settings.GlobalSettings().AutoHideWindow();
|
||||
}
|
||||
|
||||
void TerminalWindow::UpdateSettingsHandler(const winrt::IInspectable& /*sender*/,
|
||||
const winrt::TerminalApp::SettingsLoadEventArgs& arg)
|
||||
{
|
||||
UpdateSettings(arg);
|
||||
}
|
||||
|
||||
bool TerminalWindow::ShouldImmediatelyHandoffToElevated()
|
||||
{
|
||||
return _root != nullptr ? _root->ShouldImmediatelyHandoffToElevated(_settings) : false;
|
||||
|
@ -1209,12 +1219,7 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
void WindowProperties::WindowId(const uint64_t& value)
|
||||
{
|
||||
if (_WindowId != value)
|
||||
{
|
||||
_WindowId = value;
|
||||
_PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"WindowId" });
|
||||
_PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"WindowIdForDisplay" });
|
||||
}
|
||||
_WindowId = value;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "SettingsLoadEventArgs.h"
|
||||
#include "TerminalPage.h"
|
||||
#include "SettingsLoadEventArgs.h"
|
||||
|
||||
#include <inc/cppwinrt_utils.h>
|
||||
#include <ThrottledFunc.h>
|
||||
|
@ -88,6 +89,7 @@ namespace winrt::TerminalApp::implementation
|
|||
bool AutoHideWindow();
|
||||
|
||||
hstring GetWindowLayoutJson(Microsoft::Terminal::Settings::Model::LaunchPosition position);
|
||||
|
||||
void IdentifyWindow();
|
||||
void RenameFailed();
|
||||
|
||||
|
@ -125,6 +127,7 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
bool GetMinimizeToNotificationArea();
|
||||
bool GetAlwaysShowNotificationIcon();
|
||||
|
||||
bool GetShowTitleInTitlebar();
|
||||
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> ShowDialog(winrt::Windows::UI::Xaml::Controls::ContentDialog dialog);
|
||||
|
@ -135,6 +138,7 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
void WindowName(const winrt::hstring& value);
|
||||
void WindowId(const uint64_t& value);
|
||||
|
||||
bool IsQuakeWindow() const noexcept { return _WindowProperties->IsQuakeWindow(); }
|
||||
TerminalApp::WindowProperties WindowProperties() { return *_WindowProperties; }
|
||||
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
#include "pch.h"
|
||||
#include "AllShortcutActions.h"
|
||||
#include "ActionMap.h"
|
||||
#include "Command.h"
|
||||
#include "AllShortcutActions.h"
|
||||
|
||||
#include "ActionMap.g.cpp"
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Microsoft::Terminal::Control;
|
||||
using namespace winrt::Windows::Foundation::Collections;
|
||||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
|
@ -118,7 +120,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
|
||||
// Method Description:
|
||||
// - Retrieves a map of actions that can be bound to a key
|
||||
Windows::Foundation::Collections::IMapView<hstring, Model::ActionAndArgs> ActionMap::AvailableActions()
|
||||
IMapView<hstring, Model::ActionAndArgs> ActionMap::AvailableActions()
|
||||
{
|
||||
if (!_AvailableActionsCache)
|
||||
{
|
||||
|
@ -172,7 +174,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
// - Retrieves a map of command names to the commands themselves
|
||||
// - These commands should not be modified directly because they may result in
|
||||
// an invalid state for the `ActionMap`
|
||||
Windows::Foundation::Collections::IMapView<hstring, Model::Command> ActionMap::NameMap()
|
||||
IMapView<hstring, Model::Command> ActionMap::NameMap()
|
||||
{
|
||||
if (!_NameMapCache)
|
||||
{
|
||||
|
@ -283,7 +285,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
return cumulativeActions;
|
||||
}
|
||||
|
||||
Windows::Foundation::Collections::IMapView<Control::KeyChord, Model::Command> ActionMap::GlobalHotkeys()
|
||||
IMapView<Control::KeyChord, Model::Command> ActionMap::GlobalHotkeys()
|
||||
{
|
||||
if (!_GlobalHotkeysCache)
|
||||
{
|
||||
|
@ -292,7 +294,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
return _GlobalHotkeysCache.GetView();
|
||||
}
|
||||
|
||||
Windows::Foundation::Collections::IMapView<Control::KeyChord, Model::Command> ActionMap::KeyBindings()
|
||||
IMapView<Control::KeyChord, Model::Command> ActionMap::KeyBindings()
|
||||
{
|
||||
if (!_KeyBindingMapCache)
|
||||
{
|
||||
|
@ -854,4 +856,79 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
cmd->ActionAndArgs(action);
|
||||
AddAction(*cmd);
|
||||
}
|
||||
|
||||
void ActionMap::_recursiveUpdateCommandKeybindingLabels()
|
||||
{
|
||||
const auto& commands{ _ExpandedCommandsCache };
|
||||
|
||||
for (const auto& command : commands)
|
||||
{
|
||||
if (command.HasNestedCommands())
|
||||
{
|
||||
_recursiveUpdateCommandKeybindingLabels();
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there's a keybinding that's bound to exactly this command,
|
||||
// then get the keychord and display it as a
|
||||
// part of the command in the UI.
|
||||
// We specifically need to do this for nested commands.
|
||||
const auto keyChord{ GetKeyBindingForAction(command.ActionAndArgs().Action(),
|
||||
command.ActionAndArgs().Args()) };
|
||||
command.RegisterKey(keyChord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
|
||||
static bool _compareSchemeNames(const ColorScheme& lhs, const ColorScheme& rhs)
|
||||
{
|
||||
std::wstring leftName{ lhs.Name() };
|
||||
std::wstring rightName{ rhs.Name() };
|
||||
return leftName.compare(rightName) < 0;
|
||||
}
|
||||
|
||||
void ActionMap::ExpandCommands(const IVectorView<Model::Profile>& profiles,
|
||||
const IMapView<winrt::hstring, Model::ColorScheme>& schemes)
|
||||
{
|
||||
// TODO in review - It's a little weird to stash the expanded commands
|
||||
// into a separate map. Is it possible to just replace the name map with
|
||||
// the post-expanded commands?
|
||||
//
|
||||
// WHILE also making sure that upon re-saving the commands, we don't
|
||||
// actually serialize the results of the expansion. I don't think it is.
|
||||
|
||||
std::vector<Model::ColorScheme> sortedSchemes;
|
||||
sortedSchemes.reserve(schemes.Size());
|
||||
|
||||
for (const auto& nameAndScheme : schemes)
|
||||
{
|
||||
sortedSchemes.push_back(nameAndScheme.Value());
|
||||
}
|
||||
std::sort(sortedSchemes.begin(),
|
||||
sortedSchemes.end(),
|
||||
_compareSchemeNames);
|
||||
|
||||
auto copyOfCommands = winrt::single_threaded_map<winrt::hstring, Model::Command>();
|
||||
|
||||
const auto& commandsToExpand{ NameMap() };
|
||||
for (auto nameAndCommand : commandsToExpand)
|
||||
{
|
||||
copyOfCommands.Insert(nameAndCommand.Key(), nameAndCommand.Value());
|
||||
}
|
||||
|
||||
implementation::Command::ExpandCommands(copyOfCommands,
|
||||
profiles,
|
||||
winrt::param::vector_view<Model::ColorScheme>{ sortedSchemes });
|
||||
|
||||
_ExpandedCommandsCache = winrt::single_threaded_vector<Model::Command>();
|
||||
for (const auto& [_, command] : copyOfCommands)
|
||||
{
|
||||
_ExpandedCommandsCache.Append(command);
|
||||
}
|
||||
}
|
||||
IVector<Model::Command> ActionMap::ExpandedCommands()
|
||||
{
|
||||
return _ExpandedCommandsCache;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,6 +75,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
void DeleteKeyBinding(const Control::KeyChord& keys);
|
||||
void RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action);
|
||||
|
||||
Windows::Foundation::Collections::IVector<Model::Command> ExpandedCommands();
|
||||
void ExpandCommands(const Windows::Foundation::Collections::IVectorView<Model::Profile>& profiles,
|
||||
const Windows::Foundation::Collections::IMapView<winrt::hstring, Model::ColorScheme>& schemes);
|
||||
|
||||
private:
|
||||
std::optional<Model::Command> _GetActionByID(const InternalActionID actionID) const;
|
||||
std::optional<Model::Command> _GetActionByKeyChordInternal(const Control::KeyChord& keys) const;
|
||||
|
@ -90,11 +94,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
void _TryUpdateName(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd);
|
||||
void _TryUpdateKeyChord(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd);
|
||||
|
||||
void _recursiveUpdateCommandKeybindingLabels();
|
||||
|
||||
Windows::Foundation::Collections::IMap<hstring, Model::ActionAndArgs> _AvailableActionsCache{ nullptr };
|
||||
Windows::Foundation::Collections::IMap<hstring, Model::Command> _NameMapCache{ nullptr };
|
||||
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _GlobalHotkeysCache{ nullptr };
|
||||
Windows::Foundation::Collections::IMap<Control::KeyChord, Model::Command> _KeyBindingMapCache{ nullptr };
|
||||
|
||||
Windows::Foundation::Collections::IVector<Model::Command> _ExpandedCommandsCache{ nullptr };
|
||||
|
||||
std::unordered_map<winrt::hstring, Model::Command> _NestedCommands;
|
||||
std::vector<Model::Command> _IterableCommands;
|
||||
std::unordered_map<Control::KeyChord, InternalActionID, KeyChordHash, KeyChordEquality> _KeyMap;
|
||||
|
|
|
@ -20,6 +20,8 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
Windows.Foundation.Collections.IMapView<String, Command> NameMap { get; };
|
||||
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Command> KeyBindings { get; };
|
||||
Windows.Foundation.Collections.IMapView<Microsoft.Terminal.Control.KeyChord, Command> GlobalHotkeys { get; };
|
||||
|
||||
IVector<Command> ExpandedCommands { get; };
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionMap : IActionMapView
|
||||
|
|
|
@ -1214,3 +1214,8 @@ void CascadiaSettings::_validateThemeExists()
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CascadiaSettings::ExpandCommands()
|
||||
{
|
||||
_globals->ExpandCommands(ActiveProfiles().GetView(), GlobalSettings().ColorSchemes());
|
||||
}
|
||||
|
|
|
@ -143,6 +143,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
Model::DefaultTerminal CurrentDefaultTerminal() noexcept;
|
||||
void CurrentDefaultTerminal(const Model::DefaultTerminal& terminal);
|
||||
|
||||
void ExpandCommands();
|
||||
|
||||
private:
|
||||
static const std::filesystem::path& _settingsPath();
|
||||
static const std::filesystem::path& _releaseSettingsPath();
|
||||
|
|
|
@ -53,5 +53,7 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
static Boolean IsDefaultTerminalSet { get; };
|
||||
IObservableVector<DefaultTerminal> DefaultTerminals { get; };
|
||||
DefaultTerminal CurrentDefaultTerminal;
|
||||
|
||||
void ExpandCommands();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1110,6 +1110,8 @@ CascadiaSettings::CascadiaSettings(SettingsLoader&& loader) :
|
|||
_resolveDefaultProfile();
|
||||
_resolveNewTabMenuProfiles();
|
||||
_validateSettings();
|
||||
|
||||
ExpandCommands();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
|
|
@ -478,23 +478,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
// appended to this vector.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void Command::ExpandCommands(IMap<winrt::hstring, Model::Command> commands,
|
||||
void Command::ExpandCommands(IMap<winrt::hstring, Model::Command>& commands,
|
||||
IVectorView<Model::Profile> profiles,
|
||||
IVectorView<Model::ColorScheme> schemes,
|
||||
IVector<SettingsLoadWarnings> warnings)
|
||||
IVectorView<Model::ColorScheme> schemes)
|
||||
{
|
||||
std::vector<winrt::hstring> commandsToRemove;
|
||||
std::vector<Model::Command> commandsToAdd;
|
||||
|
||||
// First, collect up all the commands that need replacing.
|
||||
for (const auto& nameAndCmd : commands)
|
||||
for (const auto& [name, command] : commands)
|
||||
{
|
||||
auto cmd{ get_self<implementation::Command>(nameAndCmd.Value()) };
|
||||
auto cmd{ get_self<implementation::Command>(command) };
|
||||
|
||||
auto newCommands = _expandCommand(cmd, profiles, schemes, warnings);
|
||||
auto newCommands = _expandCommand(cmd, profiles, schemes);
|
||||
if (newCommands.size() > 0)
|
||||
{
|
||||
commandsToRemove.push_back(nameAndCmd.Key());
|
||||
commandsToRemove.push_back(name);
|
||||
commandsToAdd.insert(commandsToAdd.end(), newCommands.begin(), newCommands.end());
|
||||
}
|
||||
}
|
||||
|
@ -529,21 +528,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
// Arguments:
|
||||
// - expandable: the Command to potentially turn into more commands
|
||||
// - profiles: A list of all the profiles that this command should be expanded on.
|
||||
// - warnings: If there were any warnings during parsing, they'll be
|
||||
// appended to this vector.
|
||||
// Return Value:
|
||||
// - and empty vector if the command wasn't expandable, otherwise a list of
|
||||
// the newly-created commands.
|
||||
std::vector<Model::Command> Command::_expandCommand(Command* const expandable,
|
||||
IVectorView<Model::Profile> profiles,
|
||||
IVectorView<Model::ColorScheme> schemes,
|
||||
IVector<SettingsLoadWarnings>& warnings)
|
||||
IVectorView<Model::ColorScheme> schemes)
|
||||
{
|
||||
std::vector<Model::Command> newCommands;
|
||||
|
||||
if (expandable->HasNestedCommands())
|
||||
{
|
||||
ExpandCommands(expandable->_subcommands, profiles, schemes, warnings);
|
||||
ExpandCommands(expandable->_subcommands, profiles, schemes);
|
||||
}
|
||||
|
||||
if (expandable->_IterateOn == ExpandCommandType::None)
|
||||
|
@ -564,18 +560,19 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
const auto actualDataEnd = newJsonString.data() + newJsonString.size();
|
||||
if (!reader->parse(actualDataStart, actualDataEnd, &newJsonValue, &errs))
|
||||
{
|
||||
warnings.Append(SettingsLoadWarnings::FailedToParseCommandJson);
|
||||
// If we encounter a re-parsing error, just stop processing the rest of the commands.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Pass the new json back though FromJson, to get the new expanded value.
|
||||
std::vector<SettingsLoadWarnings> newWarnings;
|
||||
if (auto newCmd{ Command::FromJson(newJsonValue, newWarnings) })
|
||||
// FromJson requires that we pass in a vector to hang on to the
|
||||
// warnings, but ultimately, we don't care about warnings during
|
||||
// expansion.
|
||||
std::vector<SettingsLoadWarnings> unused;
|
||||
if (auto newCmd{ Command::FromJson(newJsonValue, unused) })
|
||||
{
|
||||
newCommands.push_back(*newCmd);
|
||||
}
|
||||
std::for_each(newWarnings.begin(), newWarnings.end(), [warnings](auto& warn) { warnings.Append(warn); });
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
|
@ -41,10 +41,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
static winrt::com_ptr<Command> FromJson(const Json::Value& json,
|
||||
std::vector<SettingsLoadWarnings>& warnings);
|
||||
|
||||
static void ExpandCommands(Windows::Foundation::Collections::IMap<winrt::hstring, Model::Command> commands,
|
||||
static void ExpandCommands(Windows::Foundation::Collections::IMap<winrt::hstring, Model::Command>& commands,
|
||||
Windows::Foundation::Collections::IVectorView<Model::Profile> profiles,
|
||||
Windows::Foundation::Collections::IVectorView<Model::ColorScheme> schemes,
|
||||
Windows::Foundation::Collections::IVector<SettingsLoadWarnings> warnings);
|
||||
Windows::Foundation::Collections::IVectorView<Model::ColorScheme> schemes);
|
||||
|
||||
static std::vector<SettingsLoadWarnings> LayerJson(Windows::Foundation::Collections::IMap<winrt::hstring, Model::Command>& commands,
|
||||
const Json::Value& json);
|
||||
|
@ -83,8 +82,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
|
||||
static std::vector<Model::Command> _expandCommand(Command* const expandable,
|
||||
Windows::Foundation::Collections::IVectorView<Model::Profile> profiles,
|
||||
Windows::Foundation::Collections::IVectorView<Model::ColorScheme> schemes,
|
||||
Windows::Foundation::Collections::IVector<SettingsLoadWarnings>& warnings);
|
||||
Windows::Foundation::Collections::IVectorView<Model::ColorScheme> schemes);
|
||||
friend class SettingsModelLocalTests::DeserializationTests;
|
||||
friend class SettingsModelLocalTests::CommandTests;
|
||||
};
|
||||
|
|
|
@ -42,10 +42,5 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
|
||||
Boolean HasNestedCommands { get; };
|
||||
Windows.Foundation.Collections.IMapView<String, Command> NestedCommands { get; };
|
||||
|
||||
static void ExpandCommands(Windows.Foundation.Collections.IMap<String, Command> commands,
|
||||
Windows.Foundation.Collections.IVectorView<Profile> profiles,
|
||||
Windows.Foundation.Collections.IVectorView<ColorScheme> schemes,
|
||||
Windows.Foundation.Collections.IVector<SettingsLoadWarnings> warnings);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -240,7 +240,13 @@ winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, winrt::Microso
|
|||
return _themes.GetView();
|
||||
}
|
||||
|
||||
void GlobalAppSettings::ExpandCommands(const winrt::Windows::Foundation::Collections::IVectorView<Model::Profile>& profiles,
|
||||
const winrt::Windows::Foundation::Collections::IMapView<winrt::hstring, Model::ColorScheme>& schemes)
|
||||
{
|
||||
_actionMap->ExpandCommands(profiles, schemes);
|
||||
}
|
||||
|
||||
bool GlobalAppSettings::ShouldUsePersistedLayout() const
|
||||
{
|
||||
return FirstWindowPreference() == FirstWindowPreference::PersistedWindowLayout;
|
||||
return FirstWindowPreference() == FirstWindowPreference::PersistedWindowLayout && !IsolatedMode();
|
||||
}
|
||||
|
|
|
@ -64,6 +64,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
Model::Theme CurrentTheme() noexcept;
|
||||
bool ShouldUsePersistedLayout() const;
|
||||
|
||||
void ExpandCommands(const Windows::Foundation::Collections::IVectorView<Model::Profile>& profiles,
|
||||
const Windows::Foundation::Collections::IMapView<winrt::hstring, Model::ColorScheme>& schemes);
|
||||
|
||||
INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, UnparsedDefaultProfile, L"");
|
||||
|
||||
#define GLOBAL_SETTINGS_INITIALIZE(type, name, jsonKey, ...) \
|
||||
|
|
|
@ -98,6 +98,7 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
INHERITABLE_SETTING(Boolean, ShowAdminShield);
|
||||
INHERITABLE_SETTING(IVector<NewTabMenuEntry>, NewTabMenu);
|
||||
INHERITABLE_SETTING(Boolean, EnableColorSelection);
|
||||
INHERITABLE_SETTING(Boolean, IsolatedMode);
|
||||
|
||||
Windows.Foundation.Collections.IMapView<String, ColorScheme> ColorSchemes();
|
||||
void AddColorScheme(ColorScheme scheme);
|
||||
|
@ -109,6 +110,7 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
void AddTheme(Theme theme);
|
||||
INHERITABLE_SETTING(ThemePair, Theme);
|
||||
Theme CurrentTheme { get; };
|
||||
|
||||
Boolean ShouldUsePersistedLayout();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,52 +18,53 @@ Author(s):
|
|||
// Macro format (defaultArgs are optional):
|
||||
// (type, name, jsonKey, defaultArgs)
|
||||
|
||||
#define MTSM_GLOBAL_SETTINGS(X) \
|
||||
X(int32_t, InitialRows, "initialRows", 30) \
|
||||
X(int32_t, InitialCols, "initialCols", 80) \
|
||||
X(hstring, WordDelimiters, "wordDelimiters", DEFAULT_WORD_DELIMITERS) \
|
||||
X(bool, CopyOnSelect, "copyOnSelect", false) \
|
||||
X(bool, FocusFollowMouse, "focusFollowMouse", false) \
|
||||
X(bool, ForceFullRepaintRendering, "experimental.rendering.forceFullRepaint", false) \
|
||||
X(bool, SoftwareRendering, "experimental.rendering.software", false) \
|
||||
X(bool, UseBackgroundImageForWindow, "experimental.useBackgroundImageForWindow", false) \
|
||||
X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \
|
||||
X(bool, ForceVTInput, "experimental.input.forceVT", false) \
|
||||
X(bool, TrimBlockSelection, "trimBlockSelection", true) \
|
||||
X(bool, DetectURLs, "experimental.detectURLs", true) \
|
||||
X(bool, AlwaysShowTabs, "alwaysShowTabs", true) \
|
||||
X(Model::NewTabPosition, NewTabPosition, "newTabPosition", Model::NewTabPosition::AfterLastTab) \
|
||||
X(bool, ShowTitleInTitlebar, "showTerminalTitleInTitlebar", true) \
|
||||
X(bool, ConfirmCloseAllTabs, "confirmCloseAllTabs", true) \
|
||||
X(Model::ThemePair, Theme, "theme") \
|
||||
X(hstring, Language, "language") \
|
||||
X(winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, TabWidthMode, "tabWidthMode", winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode::Equal) \
|
||||
X(bool, UseAcrylicInTabRow, "useAcrylicInTabRow", false) \
|
||||
X(bool, ShowTabsInTitlebar, "showTabsInTitlebar", true) \
|
||||
X(bool, InputServiceWarning, "inputServiceWarning", true) \
|
||||
X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, "copyFormatting", 0) \
|
||||
X(bool, WarnAboutLargePaste, "largePasteWarning", true) \
|
||||
X(bool, WarnAboutMultiLinePaste, "multiLinePasteWarning", true) \
|
||||
X(Model::LaunchPosition, InitialPosition, "initialPosition", nullptr, nullptr) \
|
||||
X(bool, CenterOnLaunch, "centerOnLaunch", false) \
|
||||
X(Model::FirstWindowPreference, FirstWindowPreference, "firstWindowPreference", FirstWindowPreference::DefaultProfile) \
|
||||
X(Model::LaunchMode, LaunchMode, "launchMode", LaunchMode::DefaultMode) \
|
||||
X(bool, SnapToGridOnResize, "snapToGridOnResize", true) \
|
||||
X(bool, DebugFeaturesEnabled, "debugFeatures", debugFeaturesDefault) \
|
||||
X(bool, StartOnUserLogin, "startOnUserLogin", false) \
|
||||
X(bool, AlwaysOnTop, "alwaysOnTop", false) \
|
||||
X(bool, AutoHideWindow, "autoHideWindow", false) \
|
||||
X(Model::TabSwitcherMode, TabSwitcherMode, "tabSwitcherMode", Model::TabSwitcherMode::InOrder) \
|
||||
X(bool, DisableAnimations, "disableAnimations", false) \
|
||||
X(hstring, StartupActions, "startupActions", L"") \
|
||||
X(Model::WindowingMode, WindowingBehavior, "windowingBehavior", Model::WindowingMode::UseNew) \
|
||||
X(bool, MinimizeToNotificationArea, "minimizeToNotificationArea", false) \
|
||||
X(bool, AlwaysShowNotificationIcon, "alwaysShowNotificationIcon", false) \
|
||||
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, "disabledProfileSources", nullptr) \
|
||||
X(bool, ShowAdminShield, "showAdminShield", true) \
|
||||
X(bool, TrimPaste, "trimPaste", true) \
|
||||
X(bool, EnableColorSelection, "experimental.enableColorSelection", false) \
|
||||
X(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, NewTabMenu, "newTabMenu", winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} }))
|
||||
#define MTSM_GLOBAL_SETTINGS(X) \
|
||||
X(int32_t, InitialRows, "initialRows", 30) \
|
||||
X(int32_t, InitialCols, "initialCols", 80) \
|
||||
X(hstring, WordDelimiters, "wordDelimiters", DEFAULT_WORD_DELIMITERS) \
|
||||
X(bool, CopyOnSelect, "copyOnSelect", false) \
|
||||
X(bool, FocusFollowMouse, "focusFollowMouse", false) \
|
||||
X(bool, ForceFullRepaintRendering, "experimental.rendering.forceFullRepaint", false) \
|
||||
X(bool, SoftwareRendering, "experimental.rendering.software", false) \
|
||||
X(bool, UseBackgroundImageForWindow, "experimental.useBackgroundImageForWindow", false) \
|
||||
X(bool, ReloadEnvironmentVariables, "compatibility.reloadEnvironmentVariables", true) \
|
||||
X(bool, ForceVTInput, "experimental.input.forceVT", false) \
|
||||
X(bool, TrimBlockSelection, "trimBlockSelection", true) \
|
||||
X(bool, DetectURLs, "experimental.detectURLs", true) \
|
||||
X(bool, AlwaysShowTabs, "alwaysShowTabs", true) \
|
||||
X(Model::NewTabPosition, NewTabPosition, "newTabPosition", Model::NewTabPosition::AfterLastTab) \
|
||||
X(bool, ShowTitleInTitlebar, "showTerminalTitleInTitlebar", true) \
|
||||
X(bool, ConfirmCloseAllTabs, "confirmCloseAllTabs", true) \
|
||||
X(Model::ThemePair, Theme, "theme") \
|
||||
X(hstring, Language, "language") \
|
||||
X(winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode, TabWidthMode, "tabWidthMode", winrt::Microsoft::UI::Xaml::Controls::TabViewWidthMode::Equal) \
|
||||
X(bool, UseAcrylicInTabRow, "useAcrylicInTabRow", false) \
|
||||
X(bool, ShowTabsInTitlebar, "showTabsInTitlebar", true) \
|
||||
X(bool, InputServiceWarning, "inputServiceWarning", true) \
|
||||
X(winrt::Microsoft::Terminal::Control::CopyFormat, CopyFormatting, "copyFormatting", 0) \
|
||||
X(bool, WarnAboutLargePaste, "largePasteWarning", true) \
|
||||
X(bool, WarnAboutMultiLinePaste, "multiLinePasteWarning", true) \
|
||||
X(Model::LaunchPosition, InitialPosition, "initialPosition", nullptr, nullptr) \
|
||||
X(bool, CenterOnLaunch, "centerOnLaunch", false) \
|
||||
X(Model::FirstWindowPreference, FirstWindowPreference, "firstWindowPreference", FirstWindowPreference::DefaultProfile) \
|
||||
X(Model::LaunchMode, LaunchMode, "launchMode", LaunchMode::DefaultMode) \
|
||||
X(bool, SnapToGridOnResize, "snapToGridOnResize", true) \
|
||||
X(bool, DebugFeaturesEnabled, "debugFeatures", debugFeaturesDefault) \
|
||||
X(bool, StartOnUserLogin, "startOnUserLogin", false) \
|
||||
X(bool, AlwaysOnTop, "alwaysOnTop", false) \
|
||||
X(bool, AutoHideWindow, "autoHideWindow", false) \
|
||||
X(Model::TabSwitcherMode, TabSwitcherMode, "tabSwitcherMode", Model::TabSwitcherMode::InOrder) \
|
||||
X(bool, DisableAnimations, "disableAnimations", false) \
|
||||
X(hstring, StartupActions, "startupActions", L"") \
|
||||
X(Model::WindowingMode, WindowingBehavior, "windowingBehavior", Model::WindowingMode::UseNew) \
|
||||
X(bool, MinimizeToNotificationArea, "minimizeToNotificationArea", false) \
|
||||
X(bool, AlwaysShowNotificationIcon, "alwaysShowNotificationIcon", false) \
|
||||
X(winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, "disabledProfileSources", nullptr) \
|
||||
X(bool, ShowAdminShield, "showAdminShield", true) \
|
||||
X(bool, TrimPaste, "trimPaste", true) \
|
||||
X(bool, EnableColorSelection, "experimental.enableColorSelection", false) \
|
||||
X(winrt::Windows::Foundation::Collections::IVector<Model::NewTabMenuEntry>, NewTabMenu, "newTabMenu", winrt::single_threaded_vector<Model::NewTabMenuEntry>({ Model::RemainingProfilesEntry{} })) \
|
||||
X(bool, IsolatedMode, "compatibility.isolatedMode", false)
|
||||
|
||||
#define MTSM_PROFILE_SETTINGS(X) \
|
||||
X(int32_t, HistorySize, "historySize", DEFAULT_HISTORY_SIZE) \
|
||||
|
|
|
@ -121,6 +121,7 @@ namespace RemotingUnitTests
|
|||
TYPED_EVENT(WindowCreated, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(WindowClosed, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
|
||||
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, Remoting::QuitAllRequestedArgs);
|
||||
TYPED_EVENT(RequestNewWindow, winrt::Windows::Foundation::IInspectable, Remoting::WindowRequestedArgs);
|
||||
};
|
||||
|
||||
class RemotingTests
|
||||
|
|
|
@ -28,32 +28,17 @@ using namespace std::chrono_literals;
|
|||
// "If the high-order bit is 1, the key is down; otherwise, it is up."
|
||||
static constexpr short KeyPressed{ gsl::narrow_cast<short>(0x8000) };
|
||||
|
||||
AppHost::AppHost() noexcept :
|
||||
_app{},
|
||||
_windowManager{},
|
||||
_appLogic{ nullptr }, // don't make one, we're going to take a ref on app's
|
||||
AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
|
||||
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
|
||||
const Remoting::WindowManager& manager,
|
||||
const Remoting::Peasant& peasant) noexcept :
|
||||
_windowManager{ manager },
|
||||
_peasant{ peasant },
|
||||
_appLogic{ logic }, // don't make one, we're going to take a ref on app's
|
||||
_windowLogic{ nullptr },
|
||||
_window{ nullptr },
|
||||
_getWindowLayoutThrottler{} // this will get set if we become the monarch
|
||||
_window{ nullptr }
|
||||
{
|
||||
_appLogic = _app.Logic(); // get a ref to app's logic
|
||||
|
||||
// Inform the WindowManager that it can use us to find the target window for
|
||||
// a set of commandline args. This needs to be done before
|
||||
// _HandleCommandlineArgs, because WE might end up being the monarch. That
|
||||
// would mean we'd need to be responsible for looking that up.
|
||||
_windowManager.FindTargetWindowRequested({ this, &AppHost::_FindTargetWindow });
|
||||
|
||||
// If there were commandline args to our process, try and process them here.
|
||||
// Do this before AppLogic::Create, otherwise this will have no effect.
|
||||
//
|
||||
// This will send our commandline to the Monarch, to ask if we should make a
|
||||
// new window or not. If not, exit immediately.
|
||||
_HandleCommandlineArgs();
|
||||
if (!_shouldCreateWindow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// _HandleCommandlineArgs will create a _windowLogic
|
||||
_useNonClientArea = _windowLogic.GetShowTabsInTitlebar();
|
||||
|
@ -96,7 +81,7 @@ AppHost::AppHost() noexcept :
|
|||
_window->MouseScrolled({ this, &AppHost::_WindowMouseWheeled });
|
||||
_window->WindowActivated({ this, &AppHost::_WindowActivated });
|
||||
_window->WindowMoved({ this, &AppHost::_WindowMoved });
|
||||
_window->HotkeyPressed({ this, &AppHost::_GlobalHotkeyPressed });
|
||||
|
||||
_window->ShouldExitFullscreen({ &_windowLogic, &winrt::TerminalApp::TerminalWindow::RequestExitFullscreen });
|
||||
|
||||
_window->SetAlwaysOnTop(_windowLogic.GetInitialAlwaysOnTop());
|
||||
|
@ -104,17 +89,12 @@ AppHost::AppHost() noexcept :
|
|||
|
||||
_window->MakeWindow();
|
||||
|
||||
_GetWindowLayoutRequestedToken = _windowManager.GetWindowLayoutRequested([this](auto&&, const winrt::Microsoft::Terminal::Remoting::GetWindowLayoutArgs& args) {
|
||||
_GetWindowLayoutRequestedToken = _peasant.GetWindowLayoutRequested([this](auto&&,
|
||||
const Remoting::GetWindowLayoutArgs& args) {
|
||||
// The peasants are running on separate threads, so they'll need to
|
||||
// swap what context they are in to the ui thread to get the actual layout.
|
||||
args.WindowLayoutJsonAsync(_GetWindowLayoutAsync());
|
||||
});
|
||||
|
||||
_revokers.BecameMonarch = _windowManager.BecameMonarch(winrt::auto_revoke, { this, &AppHost::_BecomeMonarch });
|
||||
if (_windowManager.IsMonarch())
|
||||
{
|
||||
_BecomeMonarch(nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
AppHost::~AppHost()
|
||||
|
@ -130,8 +110,6 @@ AppHost::~AppHost()
|
|||
_showHideWindowThrottler.reset();
|
||||
|
||||
_window = nullptr;
|
||||
_app.Close();
|
||||
_app = nullptr;
|
||||
}
|
||||
|
||||
bool AppHost::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down)
|
||||
|
@ -161,26 +139,17 @@ void AppHost::SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable&
|
|||
}
|
||||
}
|
||||
|
||||
void _buildArgsFromCommandline(std::vector<winrt::hstring>& args)
|
||||
void AppHost::s_DisplayMessageBox(const winrt::TerminalApp::ParseCommandlineResult& result)
|
||||
{
|
||||
if (auto commandline{ GetCommandLineW() })
|
||||
{
|
||||
auto argc = 0;
|
||||
|
||||
// Get the argv, and turn them into a hstring array to pass to the app.
|
||||
wil::unique_any<LPWSTR*, decltype(&::LocalFree), ::LocalFree> argv{ CommandLineToArgvW(commandline, &argc) };
|
||||
if (argv)
|
||||
{
|
||||
for (auto& elem : wil::make_range(argv.get(), argc))
|
||||
{
|
||||
args.emplace_back(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (args.empty())
|
||||
{
|
||||
args.emplace_back(L"wt.exe");
|
||||
}
|
||||
const auto displayHelp = result.ExitCode == 0;
|
||||
const auto messageTitle = displayHelp ? IDS_HELP_DIALOG_TITLE : IDS_ERROR_DIALOG_TITLE;
|
||||
const auto messageIcon = displayHelp ? MB_ICONWARNING : MB_ICONERROR;
|
||||
// TODO:GH#4134: polish this dialog more, to make the text more
|
||||
// like msiexec /?
|
||||
MessageBoxW(nullptr,
|
||||
result.Message.data(),
|
||||
GetStringResource(messageTitle).data(),
|
||||
MB_OK | messageIcon);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -202,44 +171,24 @@ void _buildArgsFromCommandline(std::vector<winrt::hstring>& args)
|
|||
// - <none>
|
||||
void AppHost::_HandleCommandlineArgs()
|
||||
{
|
||||
std::vector<winrt::hstring> args;
|
||||
_buildArgsFromCommandline(args);
|
||||
auto cwd{ wil::GetCurrentDirectoryW<std::wstring>() };
|
||||
|
||||
Remoting::CommandlineArgs eventArgs{ { args }, { cwd } };
|
||||
_windowManager.ProposeCommandline(eventArgs);
|
||||
|
||||
_shouldCreateWindow = _windowManager.ShouldCreateWindow();
|
||||
if (!_shouldCreateWindow)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// We did want to make a window, so let's instantiate it here.
|
||||
// We don't have XAML yet, but we do have other stuff.
|
||||
_windowLogic = _appLogic.CreateNewWindow();
|
||||
|
||||
if (auto peasant{ _windowManager.CurrentWindow() })
|
||||
if (_peasant)
|
||||
{
|
||||
if (auto args{ peasant.InitialArgs() })
|
||||
const auto& args{ _peasant.InitialArgs() };
|
||||
if (args)
|
||||
{
|
||||
const auto result = _windowLogic.SetStartupCommandline(args.Commandline());
|
||||
const auto message = _windowLogic.ParseCommandlineMessage();
|
||||
if (!message.empty())
|
||||
{
|
||||
const auto displayHelp = result == 0;
|
||||
const auto messageTitle = displayHelp ? IDS_HELP_DIALOG_TITLE : IDS_ERROR_DIALOG_TITLE;
|
||||
const auto messageIcon = displayHelp ? MB_ICONWARNING : MB_ICONERROR;
|
||||
// TODO:GH#4134: polish this dialog more, to make the text more
|
||||
// like msiexec /?
|
||||
MessageBoxW(nullptr,
|
||||
message.data(),
|
||||
GetStringResource(messageTitle).data(),
|
||||
MB_OK | messageIcon);
|
||||
AppHost::s_DisplayMessageBox({ message, result });
|
||||
|
||||
if (_windowLogic.ShouldExitEarly())
|
||||
{
|
||||
ExitProcess(result);
|
||||
ExitThread(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -264,17 +213,19 @@ void AppHost::_HandleCommandlineArgs()
|
|||
// use to send the actions to the app.
|
||||
//
|
||||
// MORE EVENT HANDLERS, same rules as the ones above.
|
||||
_revokers.peasantExecuteCommandlineRequested = peasant.ExecuteCommandlineRequested(winrt::auto_revoke, { this, &AppHost::_DispatchCommandline });
|
||||
_revokers.peasantSummonRequested = peasant.SummonRequested(winrt::auto_revoke, { this, &AppHost::_HandleSummon });
|
||||
_revokers.peasantDisplayWindowIdRequested = peasant.DisplayWindowIdRequested(winrt::auto_revoke, { this, &AppHost::_DisplayWindowId });
|
||||
_revokers.peasantQuitRequested = peasant.QuitRequested(winrt::auto_revoke, { this, &AppHost::_QuitRequested });
|
||||
_revokers.peasantExecuteCommandlineRequested = _peasant.ExecuteCommandlineRequested(winrt::auto_revoke, { this, &AppHost::_DispatchCommandline });
|
||||
_revokers.peasantSummonRequested = _peasant.SummonRequested(winrt::auto_revoke, { this, &AppHost::_HandleSummon });
|
||||
_revokers.peasantDisplayWindowIdRequested = _peasant.DisplayWindowIdRequested(winrt::auto_revoke, { this, &AppHost::_DisplayWindowId });
|
||||
_revokers.peasantQuitRequested = _peasant.QuitRequested(winrt::auto_revoke, { this, &AppHost::_QuitRequested });
|
||||
|
||||
// We need this property to be set before we get the InitialSize/Position
|
||||
// and BecameMonarch which normally sets it is only run after the window
|
||||
// is created.
|
||||
if (_windowManager.IsMonarch())
|
||||
// This is logic that almost seems like it belongs on the WindowEmperor.
|
||||
// It probably does. However, it needs to muck with our own window so
|
||||
// much, that there was no reasonable way of moving this. Moving it also
|
||||
// seemed to reorder bits of init so much that everything broke. So
|
||||
// we'll leave it here.
|
||||
const auto numPeasants = _windowManager.GetNumberOfPeasants();
|
||||
if (numPeasants == 1)
|
||||
{
|
||||
const auto numPeasants = _windowManager.GetNumberOfPeasants();
|
||||
const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts();
|
||||
if (_appLogic.ShouldUsePersistedLayout() &&
|
||||
layouts &&
|
||||
|
@ -287,7 +238,8 @@ void AppHost::_HandleCommandlineArgs()
|
|||
// Otherwise create this window normally with its commandline, and create
|
||||
// a new window using the first saved layout information.
|
||||
// The 2nd+ layout will always get a new window.
|
||||
if (numPeasants == 1 && !_windowLogic.HasCommandlineArguments() && !_appLogic.HasSettingsStartupActions())
|
||||
if (!_windowLogic.HasCommandlineArguments() &&
|
||||
!_appLogic.HasSettingsStartupActions())
|
||||
{
|
||||
_windowLogic.SetPersistedLayoutIdx(startIdx);
|
||||
startIdx += 1;
|
||||
|
@ -296,7 +248,7 @@ void AppHost::_HandleCommandlineArgs()
|
|||
// Create new windows for each of the other saved layouts.
|
||||
for (const auto size = layouts.Size(); startIdx < size; startIdx += 1)
|
||||
{
|
||||
auto newWindowArgs = fmt::format(L"{0} -w new -s {1}", args[0], startIdx);
|
||||
auto newWindowArgs = fmt::format(L"{0} -w new -s {1}", args.Commandline()[0], startIdx);
|
||||
|
||||
STARTUPINFO si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
|
@ -317,8 +269,9 @@ void AppHost::_HandleCommandlineArgs()
|
|||
}
|
||||
}
|
||||
}
|
||||
_windowLogic.WindowName(peasant.WindowName());
|
||||
_windowLogic.WindowId(peasant.GetID());
|
||||
|
||||
_windowLogic.WindowName(_peasant.WindowName());
|
||||
_windowLogic.WindowId(_peasant.GetID());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,10 +288,12 @@ void AppHost::_HandleCommandlineArgs()
|
|||
// - <none>
|
||||
void AppHost::Initialize()
|
||||
{
|
||||
// You aren't allowed to do ANY XAML before this line!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
_window->Initialize();
|
||||
|
||||
if (auto withWindow{ _windowLogic.try_as<IInitializeWithWindow>() })
|
||||
{
|
||||
// You aren't allowed to do anything with the TerminalPage before this line!!!!!!!
|
||||
withWindow->Initialize(_window->GetHandle());
|
||||
}
|
||||
|
||||
|
@ -377,7 +332,8 @@ void AppHost::Initialize()
|
|||
_window->DragRegionClicked([this]() { _windowLogic.TitlebarClicked(); });
|
||||
|
||||
_window->WindowVisibilityChanged([this](bool showOrHide) { _windowLogic.WindowVisibilityChanged(showOrHide); });
|
||||
_window->UpdateSettingsRequested([this]() { _appLogic.ReloadSettings(); });
|
||||
|
||||
_window->UpdateSettingsRequested({ this, &AppHost::_requestUpdateSettings });
|
||||
|
||||
_revokers.RequestedThemeChanged = _windowLogic.RequestedThemeChanged(winrt::auto_revoke, { this, &AppHost::_UpdateTheme });
|
||||
_revokers.FullscreenChanged = _windowLogic.FullscreenChanged(winrt::auto_revoke, { this, &AppHost::_FullscreenChanged });
|
||||
|
@ -397,7 +353,7 @@ void AppHost::Initialize()
|
|||
_window->AutomaticShutdownRequested([this]() {
|
||||
// Raised when the OS is beginning an update of the app. We will quit,
|
||||
// to save our state, before the OS manually kills us.
|
||||
_windowManager.RequestQuitAll();
|
||||
Remoting::WindowManager::RequestQuitAll(_peasant);
|
||||
});
|
||||
|
||||
// Load bearing: make sure the PropertyChanged handler is added before we
|
||||
|
@ -459,24 +415,6 @@ void AppHost::Initialize()
|
|||
// set that content as well.
|
||||
_window->SetContent(_windowLogic.GetRoot());
|
||||
_window->OnAppInitialized();
|
||||
|
||||
// BODGY
|
||||
//
|
||||
// We've got a weird crash that happens terribly inconsistently, but pretty
|
||||
// readily on migrie's laptop, only in Debug mode. Apparently, there's some
|
||||
// weird ref-counting magic that goes on during teardown, and our
|
||||
// Application doesn't get closed quite right, which can cause us to crash
|
||||
// into the debugger. This of course, only happens on exit, and happens
|
||||
// somewhere in the XamlHost.dll code.
|
||||
//
|
||||
// Crazily, if we _manually leak the Application_ here, then the crash
|
||||
// doesn't happen. This doesn't matter, because we really want the
|
||||
// Application to live for _the entire lifetime of the process_, so the only
|
||||
// time when this object would actually need to get cleaned up is _during
|
||||
// exit_. So we can safely leak this Application object, and have it just
|
||||
// get cleaned up normally when our process exits.
|
||||
auto a{ _app };
|
||||
::winrt::detach_abi(a);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -494,7 +432,7 @@ void AppHost::AppTitleChanged(const winrt::Windows::Foundation::IInspectable& /*
|
|||
{
|
||||
_window->UpdateTitle(newTitle);
|
||||
}
|
||||
_windowManager.UpdateActiveTabTitle(newTitle);
|
||||
_windowManager.UpdateActiveTabTitle(newTitle, _peasant);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -506,22 +444,18 @@ void AppHost::AppTitleChanged(const winrt::Windows::Foundation::IInspectable& /*
|
|||
// - <none>
|
||||
void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::TerminalApp::LastTabClosedEventArgs& args)
|
||||
{
|
||||
if (_windowManager.IsMonarch() && _notificationIcon)
|
||||
{
|
||||
_DestroyNotificationIcon();
|
||||
}
|
||||
else if (_window->IsQuakeWindow())
|
||||
{
|
||||
_HideNotificationIconRequested(nullptr, nullptr);
|
||||
}
|
||||
|
||||
// We don't want to try to save layouts if we are about to close.
|
||||
_getWindowLayoutThrottler.reset();
|
||||
_windowManager.GetWindowLayoutRequested(_GetWindowLayoutRequestedToken);
|
||||
// We also don't need to update any of our bookkeeping on how many
|
||||
// windows are open.
|
||||
_windowManager.WindowCreated(_WindowCreatedToken);
|
||||
_windowManager.WindowClosed(_WindowClosedToken);
|
||||
_peasant.GetWindowLayoutRequested(_GetWindowLayoutRequestedToken);
|
||||
|
||||
// If the user closes the last tab, in the last window, _by closing the tab_
|
||||
// (not by closing the whole window), we need to manually persist an empty
|
||||
// window state here. That will cause the terminal to re-open with the usual
|
||||
// settings (not the persisted state)
|
||||
if (args.ClearPersistedState() &&
|
||||
_windowManager.GetNumberOfPeasants() == 1)
|
||||
{
|
||||
_windowLogic.ClearPersistedWindowState();
|
||||
}
|
||||
|
||||
// If the user closes the last tab, in the last window, _by closing the tab_
|
||||
// (not by closing the whole window), we need to manually persist an empty
|
||||
|
@ -536,7 +470,7 @@ void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*se
|
|||
// Remove ourself from the list of peasants so that we aren't included in
|
||||
// any future requests. This will also mean we block until any existing
|
||||
// event handler finishes.
|
||||
_windowManager.SignalClose();
|
||||
_windowManager.SignalClose(_peasant);
|
||||
|
||||
_window->Close();
|
||||
}
|
||||
|
@ -887,6 +821,32 @@ void AppHost::_DispatchCommandline(winrt::Windows::Foundation::IInspectable send
|
|||
_windowLogic.ExecuteCommandline(args.Commandline(), args.CurrentDirectory());
|
||||
}
|
||||
|
||||
winrt::fire_and_forget AppHost::_WindowActivated(bool activated)
|
||||
{
|
||||
_windowLogic.WindowActivated(activated);
|
||||
|
||||
if (!activated)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
co_await winrt::resume_background();
|
||||
|
||||
if (_peasant)
|
||||
{
|
||||
const auto currentDesktopGuid{ _CurrentDesktopGuid() };
|
||||
|
||||
// TODO: projects/5 - in the future, we'll want to actually get the
|
||||
// desktop GUID in IslandWindow, and bubble that up here, then down to
|
||||
// the Peasant. For now, we're just leaving space for it.
|
||||
Remoting::WindowActivatedArgs args{ _peasant.GetID(),
|
||||
(uint64_t)_window->GetHandle(),
|
||||
currentDesktopGuid,
|
||||
winrt::clock().now() };
|
||||
_peasant.ActivateWindow(args);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Asynchronously get the window layout from the current page. This is
|
||||
// done async because we need to switch between the ui thread and the calling
|
||||
|
@ -917,306 +877,6 @@ winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> AppHost::_GetWindowL
|
|||
co_return layoutJson;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Event handler for the WindowManager::FindTargetWindowRequested event. The
|
||||
// manager will ask us how to figure out what the target window is for a set
|
||||
// of commandline arguments. We'll take those arguments, and ask AppLogic to
|
||||
// parse them for us. We'll then set ResultTargetWindow in the given args, so
|
||||
// the sender can use that result.
|
||||
// Arguments:
|
||||
// - args: the bundle of a commandline and working directory to find the correct target window for.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void AppHost::_FindTargetWindow(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const Remoting::FindTargetWindowArgs& args)
|
||||
{
|
||||
const auto targetWindow = _appLogic.FindTargetWindow(args.Args().Commandline());
|
||||
args.ResultTargetWindow(targetWindow.WindowId());
|
||||
args.ResultTargetWindowName(targetWindow.WindowName());
|
||||
}
|
||||
|
||||
winrt::fire_and_forget AppHost::_WindowActivated(bool activated)
|
||||
{
|
||||
_windowLogic.WindowActivated(activated);
|
||||
|
||||
if (!activated)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
co_await winrt::resume_background();
|
||||
|
||||
if (auto peasant{ _windowManager.CurrentWindow() })
|
||||
{
|
||||
const auto currentDesktopGuid{ _CurrentDesktopGuid() };
|
||||
|
||||
// TODO: projects/5 - in the future, we'll want to actually get the
|
||||
// desktop GUID in IslandWindow, and bubble that up here, then down to
|
||||
// the Peasant. For now, we're just leaving space for it.
|
||||
Remoting::WindowActivatedArgs args{ peasant.GetID(),
|
||||
(uint64_t)_window->GetHandle(),
|
||||
currentDesktopGuid,
|
||||
winrt::clock().now() };
|
||||
peasant.ActivateWindow(args);
|
||||
}
|
||||
}
|
||||
|
||||
void AppHost::_BecomeMonarch(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const winrt::Windows::Foundation::IInspectable& /*args*/)
|
||||
{
|
||||
// MSFT:35726322
|
||||
//
|
||||
// Although we're manually revoking the event handler now in the dtor before
|
||||
// we null out the window, let's be extra careful and check JUST IN CASE.
|
||||
if (_window == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_setupGlobalHotkeys();
|
||||
|
||||
if (_windowManager.DoesQuakeWindowExist() ||
|
||||
_window->IsQuakeWindow() ||
|
||||
(_windowLogic.GetAlwaysShowNotificationIcon() || _windowLogic.GetMinimizeToNotificationArea()))
|
||||
{
|
||||
_CreateNotificationIcon();
|
||||
}
|
||||
|
||||
_WindowCreatedToken = _windowManager.WindowCreated([this](auto&&, auto&&) {
|
||||
if (_getWindowLayoutThrottler)
|
||||
{
|
||||
_getWindowLayoutThrottler.value()();
|
||||
}
|
||||
});
|
||||
|
||||
_WindowClosedToken = _windowManager.WindowClosed([this](auto&&, auto&&) {
|
||||
if (_getWindowLayoutThrottler)
|
||||
{
|
||||
_getWindowLayoutThrottler.value()();
|
||||
}
|
||||
});
|
||||
|
||||
// These events are coming from peasants that become or un-become quake windows.
|
||||
_revokers.ShowNotificationIconRequested = _windowManager.ShowNotificationIconRequested(winrt::auto_revoke, { this, &AppHost::_ShowNotificationIconRequested });
|
||||
_revokers.HideNotificationIconRequested = _windowManager.HideNotificationIconRequested(winrt::auto_revoke, { this, &AppHost::_HideNotificationIconRequested });
|
||||
// If the monarch receives a QuitAll event it will signal this event to be
|
||||
// ran before each peasant is closed.
|
||||
_revokers.QuitAllRequested = _windowManager.QuitAllRequested(winrt::auto_revoke, { this, &AppHost::_QuitAllRequested });
|
||||
|
||||
// The monarch should be monitoring if it should save the window layout.
|
||||
if (!_getWindowLayoutThrottler.has_value())
|
||||
{
|
||||
// We want at least some delay to prevent the first save from overwriting
|
||||
// the data as we try load windows initially.
|
||||
_getWindowLayoutThrottler.emplace(std::move(std::chrono::seconds(10)), std::move([this]() { _SaveWindowLayoutsRepeat(); }));
|
||||
_getWindowLayoutThrottler.value()();
|
||||
}
|
||||
}
|
||||
|
||||
winrt::Windows::Foundation::IAsyncAction AppHost::_SaveWindowLayouts()
|
||||
{
|
||||
// Make sure we run on a background thread to not block anything.
|
||||
co_await winrt::resume_background();
|
||||
|
||||
if (_appLogic.ShouldUsePersistedLayout())
|
||||
{
|
||||
try
|
||||
{
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_SaveWindowLayouts_Collect",
|
||||
TraceLoggingDescription("Logged when collecting window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
const auto layoutJsons = _windowManager.GetAllWindowLayouts();
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_SaveWindowLayouts_Save",
|
||||
TraceLoggingDescription("Logged when writing window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
_appLogic.SaveWindowLayoutJsons(layoutJsons);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_SaveWindowLayouts_Failed",
|
||||
TraceLoggingDescription("An error occurred when collecting or writing window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
winrt::fire_and_forget AppHost::_SaveWindowLayoutsRepeat()
|
||||
{
|
||||
// Make sure we run on a background thread to not block anything.
|
||||
co_await winrt::resume_background();
|
||||
|
||||
co_await _SaveWindowLayouts();
|
||||
|
||||
// Don't need to save too frequently.
|
||||
co_await winrt::resume_after(30s);
|
||||
|
||||
// As long as we are supposed to keep saving, request another save.
|
||||
// This will be delayed by the throttler so that at most one save happens
|
||||
// per 10 seconds, if a save is requested by another source simultaneously.
|
||||
if (_getWindowLayoutThrottler.has_value())
|
||||
{
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_requestGetLayout",
|
||||
TraceLoggingDescription("Logged when triggering a throttled write of the window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
_getWindowLayoutThrottler.value()();
|
||||
}
|
||||
}
|
||||
|
||||
winrt::fire_and_forget AppHost::_setupGlobalHotkeys()
|
||||
{
|
||||
// The hotkey MUST be registered on the main thread. It will fail otherwise!
|
||||
co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher());
|
||||
|
||||
if (!_window)
|
||||
{
|
||||
// MSFT:36797001 There's a surprising number of hits of this callback
|
||||
// getting triggered during teardown. As a best practice, we really
|
||||
// should make sure _window exists before accessing it on any coroutine.
|
||||
// We might be getting called back after the app already began getting
|
||||
// cleaned up.
|
||||
co_return;
|
||||
}
|
||||
// Unregister all previously registered hotkeys.
|
||||
//
|
||||
// RegisterHotKey(), will not unregister hotkeys automatically.
|
||||
// If a hotkey with a given HWND and ID combination already exists
|
||||
// then a duplicate one will be added, which we don't want.
|
||||
// (Additionally we want to remove hotkeys that were removed from the settings.)
|
||||
for (auto i = 0, count = gsl::narrow_cast<int>(_hotkeys.size()); i < count; ++i)
|
||||
{
|
||||
_window->UnregisterHotKey(i);
|
||||
}
|
||||
|
||||
_hotkeys.clear();
|
||||
|
||||
// Re-register all current hotkeys.
|
||||
for (const auto& [keyChord, cmd] : _appLogic.GlobalHotkeys())
|
||||
{
|
||||
if (auto summonArgs = cmd.ActionAndArgs().Args().try_as<Settings::Model::GlobalSummonArgs>())
|
||||
{
|
||||
auto index = gsl::narrow_cast<int>(_hotkeys.size());
|
||||
const auto succeeded = _window->RegisterHotKey(index, keyChord);
|
||||
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_setupGlobalHotkey",
|
||||
TraceLoggingDescription("Emitted when setting a single hotkey"),
|
||||
TraceLoggingInt64(index, "index", "the index of the hotkey to add"),
|
||||
TraceLoggingWideString(cmd.Name().c_str(), "name", "the name of the command"),
|
||||
TraceLoggingBoolean(succeeded, "succeeded", "true if we succeeded"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
_hotkeys.emplace_back(summonArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called whenever a registered hotkey is pressed. We'll look up the
|
||||
// GlobalSummonArgs for the specified hotkey, then dispatch a call to the
|
||||
// Monarch with the selection information.
|
||||
// - If the monarch finds a match for the window name (or no name was provided),
|
||||
// it'll set FoundMatch=true.
|
||||
// - If FoundMatch is false, and a name was provided, then we should create a
|
||||
// new window with the given name.
|
||||
// Arguments:
|
||||
// - hotkeyIndex: the index of the entry in _hotkeys that was pressed.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void AppHost::_GlobalHotkeyPressed(const long hotkeyIndex)
|
||||
{
|
||||
if (hotkeyIndex < 0 || static_cast<size_t>(hotkeyIndex) > _hotkeys.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& summonArgs = til::at(_hotkeys, hotkeyIndex);
|
||||
Remoting::SummonWindowSelectionArgs args{ summonArgs.Name() };
|
||||
|
||||
// desktop:any - MoveToCurrentDesktop=false, OnCurrentDesktop=false
|
||||
// desktop:toCurrent - MoveToCurrentDesktop=true, OnCurrentDesktop=false
|
||||
// desktop:onCurrent - MoveToCurrentDesktop=false, OnCurrentDesktop=true
|
||||
args.OnCurrentDesktop(summonArgs.Desktop() == Settings::Model::DesktopBehavior::OnCurrent);
|
||||
args.SummonBehavior().MoveToCurrentDesktop(summonArgs.Desktop() == Settings::Model::DesktopBehavior::ToCurrent);
|
||||
args.SummonBehavior().ToggleVisibility(summonArgs.ToggleVisibility());
|
||||
args.SummonBehavior().DropdownDuration(summonArgs.DropdownDuration());
|
||||
|
||||
switch (summonArgs.Monitor())
|
||||
{
|
||||
case Settings::Model::MonitorBehavior::Any:
|
||||
args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::InPlace);
|
||||
break;
|
||||
case Settings::Model::MonitorBehavior::ToCurrent:
|
||||
args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::ToCurrent);
|
||||
break;
|
||||
case Settings::Model::MonitorBehavior::ToMouse:
|
||||
args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::ToMouse);
|
||||
break;
|
||||
}
|
||||
|
||||
_windowManager.SummonWindow(args);
|
||||
if (args.FoundMatch())
|
||||
{
|
||||
// Excellent, the window was found. We have nothing else to do here.
|
||||
}
|
||||
else
|
||||
{
|
||||
// We should make the window ourselves.
|
||||
_createNewTerminalWindow(summonArgs);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Called when the monarch failed to summon a window for a given set of
|
||||
// SummonWindowSelectionArgs. In this case, we should create the specified
|
||||
// window ourselves.
|
||||
// - This is to support the scenario like `globalSummon(Name="_quake")` being
|
||||
// used to summon the window if it already exists, or create it if it doesn't.
|
||||
// Arguments:
|
||||
// - args: Contains information on how we should name the window
|
||||
// Return Value:
|
||||
// - <none>
|
||||
winrt::fire_and_forget AppHost::_createNewTerminalWindow(Settings::Model::GlobalSummonArgs args)
|
||||
{
|
||||
// Hop to the BG thread
|
||||
co_await winrt::resume_background();
|
||||
|
||||
// This will get us the correct exe for dev/preview/release. If you
|
||||
// don't stick this in a local, it'll get mangled by ShellExecute. I
|
||||
// have no idea why.
|
||||
const auto exePath{ GetWtExePath() };
|
||||
|
||||
// If we weren't given a name, then just use new to force the window to be
|
||||
// unnamed.
|
||||
winrt::hstring cmdline{
|
||||
fmt::format(L"-w {}",
|
||||
args.Name().empty() ? L"new" :
|
||||
args.Name())
|
||||
};
|
||||
|
||||
SHELLEXECUTEINFOW seInfo{ 0 };
|
||||
seInfo.cbSize = sizeof(seInfo);
|
||||
seInfo.fMask = SEE_MASK_NOASYNC;
|
||||
seInfo.lpVerb = L"open";
|
||||
seInfo.lpFile = exePath.c_str();
|
||||
seInfo.lpParameters = cmdline.c_str();
|
||||
seInfo.nShow = SW_SHOWNORMAL;
|
||||
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Helper to initialize our instance of IVirtualDesktopManager. If we already
|
||||
// got one, then this will just return true. Otherwise, we'll try and init a
|
||||
|
@ -1308,9 +968,9 @@ winrt::fire_and_forget AppHost::_IdentifyWindowsRequested(const winrt::Windows::
|
|||
// make sure we're on the background thread, or this will silently fail
|
||||
co_await winrt::resume_background();
|
||||
|
||||
if (auto peasant{ _windowManager.CurrentWindow() })
|
||||
if (_peasant)
|
||||
{
|
||||
peasant.RequestIdentifyWindows();
|
||||
_peasant.RequestIdentifyWindows();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1336,11 +996,11 @@ winrt::fire_and_forget AppHost::_RenameWindowRequested(const winrt::Windows::Fou
|
|||
// Switch to the BG thread - anything x-proc must happen on a BG thread
|
||||
co_await winrt::resume_background();
|
||||
|
||||
if (auto peasant{ _windowManager.CurrentWindow() })
|
||||
if (_peasant)
|
||||
{
|
||||
Remoting::RenameRequestArgs requestArgs{ args.ProposedName() };
|
||||
|
||||
peasant.RequestRename(requestArgs);
|
||||
_peasant.RequestRename(requestArgs);
|
||||
|
||||
// Switch back to the UI thread. Setting the WindowName needs to happen
|
||||
// on the UI thread, because it'll raise a PropertyChanged event
|
||||
|
@ -1407,32 +1067,7 @@ void AppHost::_updateTheme()
|
|||
void AppHost::_HandleSettingsChanged(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const winrt::TerminalApp::SettingsLoadEventArgs& /*args*/)
|
||||
{
|
||||
_setupGlobalHotkeys();
|
||||
|
||||
// If we're monarch, we need to check some conditions to show the notification icon.
|
||||
// If there's a Quake window somewhere, we'll want to keep the notification icon.
|
||||
// There's two settings - MinimizeToNotificationArea and AlwaysShowNotificationIcon. If either
|
||||
// one of them are true, we want to make sure there's a notification icon.
|
||||
// If both are false, we want to remove our icon from the notification area.
|
||||
// When we remove our icon from the notification area, we'll also want to re-summon
|
||||
// any hidden windows, but right now we're not keeping track of who's hidden,
|
||||
// so just summon them all. Tracking the work to do a "summon all minimized" in
|
||||
// GH#10448
|
||||
if (_windowManager.IsMonarch())
|
||||
{
|
||||
if (!_windowManager.DoesQuakeWindowExist())
|
||||
{
|
||||
if (!_notificationIcon && (_windowLogic.GetMinimizeToNotificationArea() || _windowLogic.GetAlwaysShowNotificationIcon()))
|
||||
{
|
||||
_CreateNotificationIcon();
|
||||
}
|
||||
else if (_notificationIcon && !_windowLogic.GetMinimizeToNotificationArea() && !_windowLogic.GetAlwaysShowNotificationIcon())
|
||||
{
|
||||
_windowManager.SummonAllWindows();
|
||||
_DestroyNotificationIcon();
|
||||
}
|
||||
}
|
||||
}
|
||||
// We don't need to call in to windowLogic here - it has its own SettingsChanged handler
|
||||
|
||||
_window->SetMinimizeToNotificationAreaBehavior(_windowLogic.GetMinimizeToNotificationArea());
|
||||
_window->SetAutoHideWindow(_windowLogic.AutoHideWindow());
|
||||
|
@ -1442,23 +1077,10 @@ void AppHost::_HandleSettingsChanged(const winrt::Windows::Foundation::IInspecta
|
|||
void AppHost::_IsQuakeWindowChanged(const winrt::Windows::Foundation::IInspectable&,
|
||||
const winrt::Windows::Foundation::IInspectable&)
|
||||
{
|
||||
// We want the quake window to be accessible through the notification icon.
|
||||
// This means if there's a quake window _somewhere_, we want the notification icon
|
||||
// to show regardless of the notification icon settings.
|
||||
// This also means we'll need to destroy the notification icon if it was created
|
||||
// specifically for the quake window. If not, it should not be destroyed.
|
||||
if (!_window->IsQuakeWindow() && _windowLogic.IsQuakeWindow())
|
||||
{
|
||||
_ShowNotificationIconRequested(nullptr, nullptr);
|
||||
}
|
||||
else if (_window->IsQuakeWindow() && !_windowLogic.IsQuakeWindow())
|
||||
{
|
||||
_HideNotificationIconRequested(nullptr, nullptr);
|
||||
}
|
||||
|
||||
_window->IsQuakeWindow(_windowLogic.IsQuakeWindow());
|
||||
}
|
||||
|
||||
// Raised from our Peasant. We handle by propagating the call to our terminal window.
|
||||
winrt::fire_and_forget AppHost::_QuitRequested(const winrt::Windows::Foundation::IInspectable&,
|
||||
const winrt::Windows::Foundation::IInspectable&)
|
||||
{
|
||||
|
@ -1468,25 +1090,11 @@ winrt::fire_and_forget AppHost::_QuitRequested(const winrt::Windows::Foundation:
|
|||
_windowLogic.Quit();
|
||||
}
|
||||
|
||||
// Raised from TerminalWindow. We handle by bubbling the request to the window manager.
|
||||
void AppHost::_RequestQuitAll(const winrt::Windows::Foundation::IInspectable&,
|
||||
const winrt::Windows::Foundation::IInspectable&)
|
||||
{
|
||||
_windowManager.RequestQuitAll();
|
||||
}
|
||||
|
||||
void AppHost::_QuitAllRequested(const winrt::Windows::Foundation::IInspectable&,
|
||||
const winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs& args)
|
||||
{
|
||||
// Make sure that the current timer is destroyed so that it doesn't attempt
|
||||
// to run while we are in the middle of quitting.
|
||||
if (_getWindowLayoutThrottler.has_value())
|
||||
{
|
||||
_getWindowLayoutThrottler.reset();
|
||||
}
|
||||
|
||||
// Tell the monarch to wait for the window layouts to save before
|
||||
// everyone quits.
|
||||
args.BeforeQuitAllAction(_SaveWindowLayouts());
|
||||
Remoting::WindowManager::RequestQuitAll(_peasant);
|
||||
}
|
||||
|
||||
void AppHost::_ShowWindowChanged(const winrt::Windows::Foundation::IInspectable&,
|
||||
|
@ -1540,76 +1148,6 @@ void AppHost::_SystemMenuChangeRequested(const winrt::Windows::Foundation::IInsp
|
|||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Creates a Notification Icon and hooks up its handlers
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void AppHost::_CreateNotificationIcon()
|
||||
{
|
||||
_notificationIcon = std::make_unique<NotificationIcon>(_window->GetHandle());
|
||||
|
||||
// Hookup the handlers, save the tokens for revoking if settings change.
|
||||
_ReAddNotificationIconToken = _window->NotifyReAddNotificationIcon([this]() { _notificationIcon->ReAddNotificationIcon(); });
|
||||
_NotificationIconPressedToken = _window->NotifyNotificationIconPressed([this]() { _notificationIcon->NotificationIconPressed(); });
|
||||
_ShowNotificationIconContextMenuToken = _window->NotifyShowNotificationIconContextMenu([this](til::point coord) { _notificationIcon->ShowContextMenu(coord, _windowManager.GetPeasantInfos()); });
|
||||
_NotificationIconMenuItemSelectedToken = _window->NotifyNotificationIconMenuItemSelected([this](HMENU hm, UINT idx) { _notificationIcon->MenuItemSelected(hm, idx); });
|
||||
_notificationIcon->SummonWindowRequested([this](auto& args) { _windowManager.SummonWindow(args); });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Deletes our notification icon if we have one.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void AppHost::_DestroyNotificationIcon()
|
||||
{
|
||||
_window->NotifyReAddNotificationIcon(_ReAddNotificationIconToken);
|
||||
_window->NotifyNotificationIconPressed(_NotificationIconPressedToken);
|
||||
_window->NotifyShowNotificationIconContextMenu(_ShowNotificationIconContextMenuToken);
|
||||
_window->NotifyNotificationIconMenuItemSelected(_NotificationIconMenuItemSelectedToken);
|
||||
|
||||
_notificationIcon->RemoveIconFromNotificationArea();
|
||||
_notificationIcon = nullptr;
|
||||
}
|
||||
|
||||
void AppHost::_ShowNotificationIconRequested(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const winrt::Windows::Foundation::IInspectable& /*args*/)
|
||||
{
|
||||
if (_windowManager.IsMonarch())
|
||||
{
|
||||
if (!_notificationIcon)
|
||||
{
|
||||
_CreateNotificationIcon();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_windowManager.RequestShowNotificationIcon();
|
||||
}
|
||||
}
|
||||
|
||||
void AppHost::_HideNotificationIconRequested(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const winrt::Windows::Foundation::IInspectable& /*args*/)
|
||||
{
|
||||
if (_windowManager.IsMonarch())
|
||||
{
|
||||
// Destroy it only if our settings allow it
|
||||
if (_notificationIcon &&
|
||||
!_windowLogic.GetAlwaysShowNotificationIcon() &&
|
||||
!_windowLogic.GetMinimizeToNotificationArea())
|
||||
{
|
||||
_DestroyNotificationIcon();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_windowManager.RequestHideNotificationIcon();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - BODGY workaround for GH#9320. When the window moves, dismiss all the popups
|
||||
// in the UI tree. Xaml Islands unfortunately doesn't do this for us, see
|
||||
|
@ -1673,3 +1211,16 @@ void AppHost::_PropertyChangedHandler(const winrt::Windows::Foundation::IInspect
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
winrt::TerminalApp::TerminalWindow AppHost::Logic()
|
||||
{
|
||||
return _windowLogic;
|
||||
}
|
||||
|
||||
// Bubble the update settings request up to the emperor. We're being called on
|
||||
// the Window thread, but the Emperor needs to update the settings on the _main_
|
||||
// thread.
|
||||
void AppHost::_requestUpdateSettings()
|
||||
{
|
||||
_UpdateSettingsRequestedHandlers();
|
||||
}
|
||||
|
|
|
@ -9,7 +9,10 @@
|
|||
class AppHost
|
||||
{
|
||||
public:
|
||||
AppHost() noexcept;
|
||||
AppHost(const winrt::TerminalApp::AppLogic& logic,
|
||||
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
|
||||
const winrt::Microsoft::Terminal::Remoting::WindowManager& manager,
|
||||
const winrt::Microsoft::Terminal::Remoting::Peasant& peasant) noexcept;
|
||||
virtual ~AppHost();
|
||||
|
||||
void AppTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, winrt::hstring newTitle);
|
||||
|
@ -21,24 +24,27 @@ public:
|
|||
bool HasWindow();
|
||||
winrt::TerminalApp::TerminalWindow Logic();
|
||||
|
||||
static void s_DisplayMessageBox(const winrt::TerminalApp::ParseCommandlineResult& message);
|
||||
|
||||
WINRT_CALLBACK(UpdateSettingsRequested, winrt::delegate<void()>);
|
||||
|
||||
private:
|
||||
std::unique_ptr<IslandWindow> _window;
|
||||
winrt::TerminalApp::App _app;
|
||||
|
||||
winrt::TerminalApp::AppLogic _appLogic;
|
||||
winrt::TerminalApp::TerminalWindow _windowLogic;
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::WindowManager _windowManager{ nullptr };
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant _peasant{ nullptr };
|
||||
|
||||
std::vector<winrt::Microsoft::Terminal::Settings::Model::GlobalSummonArgs> _hotkeys;
|
||||
winrt::com_ptr<IVirtualDesktopManager> _desktopManager{ nullptr };
|
||||
|
||||
bool _shouldCreateWindow{ false };
|
||||
bool _useNonClientArea{ false };
|
||||
|
||||
std::optional<til::throttled_func_trailing<>> _getWindowLayoutThrottler;
|
||||
std::shared_ptr<ThrottledFuncTrailing<bool>> _showHideWindowThrottler;
|
||||
winrt::Windows::Foundation::IAsyncAction _SaveWindowLayouts();
|
||||
winrt::fire_and_forget _SaveWindowLayoutsRepeat();
|
||||
|
||||
void _preInit();
|
||||
|
||||
void _HandleCommandlineArgs();
|
||||
winrt::Microsoft::Terminal::Settings::Model::LaunchPosition _GetWindowLaunchPosition();
|
||||
|
@ -67,12 +73,6 @@ private:
|
|||
|
||||
winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> _GetWindowLayoutAsync();
|
||||
|
||||
void _FindTargetWindow(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& args);
|
||||
|
||||
void _BecomeMonarch(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& args);
|
||||
void _GlobalHotkeyPressed(const long hotkeyIndex);
|
||||
void _HandleSummon(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior& args);
|
||||
|
||||
|
@ -87,9 +87,6 @@ private:
|
|||
|
||||
bool _LazyLoadDesktopManager();
|
||||
|
||||
void _listenForInboundConnections();
|
||||
winrt::fire_and_forget _setupGlobalHotkeys();
|
||||
winrt::fire_and_forget _createNewTerminalWindow(winrt::Microsoft::Terminal::Settings::Model::GlobalSummonArgs args);
|
||||
void _HandleSettingsChanged(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::TerminalApp::SettingsLoadEventArgs& args);
|
||||
|
||||
|
@ -119,13 +116,6 @@ private:
|
|||
void _ShowWindowChanged(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Microsoft::Terminal::Control::ShowWindowArgs& args);
|
||||
|
||||
void _CreateNotificationIcon();
|
||||
void _DestroyNotificationIcon();
|
||||
void _ShowNotificationIconRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& args);
|
||||
void _HideNotificationIconRequested(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
const winrt::Windows::Foundation::IInspectable& args);
|
||||
|
||||
void _updateTheme();
|
||||
|
||||
void _PropertyChangedHandler(const winrt::Windows::Foundation::IInspectable& sender,
|
||||
|
@ -133,14 +123,9 @@ private:
|
|||
|
||||
void _initialResizeAndRepositionWindow(const HWND hwnd, RECT proposedRect, winrt::Microsoft::Terminal::Settings::Model::LaunchMode& launchMode);
|
||||
|
||||
std::unique_ptr<NotificationIcon> _notificationIcon;
|
||||
winrt::event_token _ReAddNotificationIconToken;
|
||||
winrt::event_token _NotificationIconPressedToken;
|
||||
winrt::event_token _ShowNotificationIconContextMenuToken;
|
||||
winrt::event_token _NotificationIconMenuItemSelectedToken;
|
||||
void _requestUpdateSettings();
|
||||
|
||||
winrt::event_token _GetWindowLayoutRequestedToken;
|
||||
winrt::event_token _WindowCreatedToken;
|
||||
winrt::event_token _WindowClosedToken;
|
||||
|
||||
// Helper struct. By putting these all into one struct, we can revoke them
|
||||
// all at once, by assigning _revokers to a fresh Revokers instance. That'll
|
||||
|
@ -149,7 +134,6 @@ private:
|
|||
struct Revokers
|
||||
{
|
||||
// Event handlers to revoke in ~AppHost, before calling App.Close
|
||||
winrt::Microsoft::Terminal::Remoting::WindowManager::BecameMonarch_revoker BecameMonarch;
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant::ExecuteCommandlineRequested_revoker peasantExecuteCommandlineRequested;
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant::SummonRequested_revoker peasantSummonRequested;
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant::DisplayWindowIdRequested_revoker peasantDisplayWindowIdRequested;
|
||||
|
@ -174,8 +158,7 @@ private:
|
|||
winrt::TerminalApp::TerminalWindow::ShowWindowChanged_revoker ShowWindowChanged;
|
||||
winrt::TerminalApp::TerminalWindow::PropertyChanged_revoker PropertyChanged;
|
||||
winrt::TerminalApp::TerminalWindow::SettingsChanged_revoker SettingsChanged;
|
||||
winrt::Microsoft::Terminal::Remoting::WindowManager::ShowNotificationIconRequested_revoker ShowNotificationIconRequested;
|
||||
winrt::Microsoft::Terminal::Remoting::WindowManager::HideNotificationIconRequested_revoker HideNotificationIconRequested;
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::WindowManager::QuitAllRequested_revoker QuitAllRequested;
|
||||
} _revokers{};
|
||||
};
|
||||
|
|
|
@ -27,8 +27,6 @@ using VirtualKeyModifiers = winrt::Windows::System::VirtualKeyModifiers;
|
|||
#define XAML_HOSTING_WINDOW_CLASS_NAME L"CASCADIA_HOSTING_WINDOW_CLASS"
|
||||
#define IDM_SYSTEM_MENU_BEGIN 0x1000
|
||||
|
||||
const UINT WM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated");
|
||||
|
||||
IslandWindow::IslandWindow() noexcept :
|
||||
_interopWindowHandle{ nullptr },
|
||||
_rootGrid{ nullptr },
|
||||
|
@ -418,11 +416,6 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
|
|||
{
|
||||
switch (message)
|
||||
{
|
||||
case WM_HOTKEY:
|
||||
{
|
||||
_HotkeyPressedHandlers(static_cast<long>(wparam));
|
||||
return 0;
|
||||
}
|
||||
case WM_GETMINMAXINFO:
|
||||
{
|
||||
_OnGetMinMaxInfo(wparam, lparam);
|
||||
|
@ -638,30 +631,6 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
|
|||
}
|
||||
break;
|
||||
}
|
||||
case CM_NOTIFY_FROM_NOTIFICATION_AREA:
|
||||
{
|
||||
switch (LOWORD(lparam))
|
||||
{
|
||||
case NIN_SELECT:
|
||||
case NIN_KEYSELECT:
|
||||
{
|
||||
_NotifyNotificationIconPressedHandlers();
|
||||
return 0;
|
||||
}
|
||||
case WM_CONTEXTMENU:
|
||||
{
|
||||
const til::point eventPoint{ GET_X_LPARAM(wparam), GET_Y_LPARAM(wparam) };
|
||||
_NotifyShowNotificationIconContextMenuHandlers(eventPoint);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_MENUCOMMAND:
|
||||
{
|
||||
_NotifyNotificationIconMenuItemSelectedHandlers((HMENU)lparam, (UINT)wparam);
|
||||
return 0;
|
||||
}
|
||||
case WM_SYSCOMMAND:
|
||||
{
|
||||
// the low 4 bits contain additional information (that we don't care about)
|
||||
|
@ -738,16 +707,6 @@ long IslandWindow::_calculateTotalSize(const bool isWidth, const long clientSize
|
|||
_AutomaticShutdownRequestedHandlers();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
// We'll want to receive this message when explorer.exe restarts
|
||||
// so that we can re-add our icon to the notification area.
|
||||
// This unfortunately isn't a switch case because we register the
|
||||
// message at runtime.
|
||||
if (message == WM_TASKBARCREATED)
|
||||
{
|
||||
_NotifyReAddNotificationIconHandlers();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: handle messages here...
|
||||
|
@ -1264,65 +1223,6 @@ void IslandWindow::_SetIsFullscreen(const bool fullscreenEnabled)
|
|||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Call UnregisterHotKey once for each previously registered hotkey.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void IslandWindow::UnregisterHotKey(const int index) noexcept
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hWindowsTerminalProvider,
|
||||
"UnregisterHotKey",
|
||||
TraceLoggingDescription("Emitted when clearing previously set hotkeys"),
|
||||
TraceLoggingInt64(index, "index", "the index of the hotkey to remove"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
LOG_IF_WIN32_BOOL_FALSE(::UnregisterHotKey(_window.get(), index));
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Call RegisterHotKey to attempt to register that keybinding as a global hotkey.
|
||||
// - When these keys are pressed, we'll get a WM_HOTKEY message with the payload
|
||||
// containing the index we registered here.
|
||||
// - Call UnregisterHotKey() before registering your hotkeys.
|
||||
// See: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerhotkey#remarks
|
||||
// Arguments:
|
||||
// - hotkey: The key-combination to register.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
bool IslandWindow::RegisterHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept
|
||||
{
|
||||
const auto vkey = hotkey.Vkey();
|
||||
auto hotkeyFlags = MOD_NOREPEAT;
|
||||
{
|
||||
const auto modifiers = hotkey.Modifiers();
|
||||
WI_SetFlagIf(hotkeyFlags, MOD_WIN, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Windows));
|
||||
WI_SetFlagIf(hotkeyFlags, MOD_ALT, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Menu));
|
||||
WI_SetFlagIf(hotkeyFlags, MOD_CONTROL, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Control));
|
||||
WI_SetFlagIf(hotkeyFlags, MOD_SHIFT, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Shift));
|
||||
}
|
||||
|
||||
// TODO GH#8888: We should display a warning of some kind if this fails.
|
||||
// This can fail if something else already bound this hotkey.
|
||||
const auto result = ::RegisterHotKey(_window.get(), index, hotkeyFlags, vkey);
|
||||
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"RegisterHotKey",
|
||||
TraceLoggingDescription("Emitted when setting hotkeys"),
|
||||
TraceLoggingInt64(index, "index", "the index of the hotkey to add"),
|
||||
TraceLoggingUInt64(vkey, "vkey", "the key"),
|
||||
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_WIN), "win", "is WIN in the modifiers"),
|
||||
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_ALT), "alt", "is ALT in the modifiers"),
|
||||
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_CONTROL), "control", "is CONTROL in the modifiers"),
|
||||
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_SHIFT), "shift", "is SHIFT in the modifiers"),
|
||||
TraceLoggingBool(result, "succeeded", "true if we succeeded"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Summon the window, or possibly dismiss it. If toggleVisibility is true,
|
||||
// then we'll dismiss (minimize) the window if it's currently active.
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
|
||||
#include "pch.h"
|
||||
#include "BaseWindow.h"
|
||||
#include <winrt/TerminalApp.h>
|
||||
|
||||
void SetWindowLongWHelper(const HWND hWnd, const int nIndex, const LONG dwNewLong) noexcept;
|
||||
|
||||
|
@ -51,9 +50,6 @@ public:
|
|||
void FlashTaskbar();
|
||||
void SetTaskbarProgress(const size_t state, const size_t progress);
|
||||
|
||||
void UnregisterHotKey(const int index) noexcept;
|
||||
bool RegisterHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept;
|
||||
|
||||
winrt::fire_and_forget SummonWindow(winrt::Microsoft::Terminal::Remoting::SummonWindowBehavior args);
|
||||
|
||||
bool IsQuakeWindow() const noexcept;
|
||||
|
@ -74,7 +70,6 @@ public:
|
|||
WINRT_CALLBACK(WindowCloseButtonClicked, winrt::delegate<>);
|
||||
WINRT_CALLBACK(MouseScrolled, winrt::delegate<void(til::point, int32_t)>);
|
||||
WINRT_CALLBACK(WindowActivated, winrt::delegate<void(bool)>);
|
||||
WINRT_CALLBACK(HotkeyPressed, winrt::delegate<void(long)>);
|
||||
WINRT_CALLBACK(NotifyNotificationIconPressed, winrt::delegate<void()>);
|
||||
WINRT_CALLBACK(NotifyWindowHidden, winrt::delegate<void()>);
|
||||
WINRT_CALLBACK(NotifyShowNotificationIconContextMenu, winrt::delegate<void(til::point)>);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "pch.h"
|
||||
|
||||
#pragma once
|
||||
// This enumerates all the possible actions
|
||||
// that our notification icon context menu could do.
|
||||
enum class NotificationIconMenuItemAction
|
||||
|
|
|
@ -0,0 +1,757 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "WindowEmperor.h"
|
||||
|
||||
#include "../inc/WindowingBehavior.h"
|
||||
|
||||
#include "../../types/inc/utils.hpp"
|
||||
|
||||
#include "../WinRTUtils/inc/WtExeUtils.h"
|
||||
|
||||
#include "resource.h"
|
||||
#include "NotificationIcon.h"
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::Microsoft::Terminal;
|
||||
using namespace winrt::Microsoft::Terminal::Settings::Model;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
using namespace ::Microsoft::Console;
|
||||
using namespace std::chrono_literals;
|
||||
using VirtualKeyModifiers = winrt::Windows::System::VirtualKeyModifiers;
|
||||
|
||||
#define TERMINAL_MESSAGE_CLASS_NAME L"TERMINAL_MESSAGE_CLASS"
|
||||
extern "C" IMAGE_DOS_HEADER __ImageBase;
|
||||
|
||||
WindowEmperor::WindowEmperor() noexcept :
|
||||
_app{}
|
||||
{
|
||||
_manager.FindTargetWindowRequested([this](const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs& findWindowArgs) {
|
||||
{
|
||||
const auto targetWindow = _app.Logic().FindTargetWindow(findWindowArgs.Args().Commandline());
|
||||
findWindowArgs.ResultTargetWindow(targetWindow.WindowId());
|
||||
findWindowArgs.ResultTargetWindowName(targetWindow.WindowName());
|
||||
}
|
||||
});
|
||||
|
||||
_dispatcher = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
|
||||
}
|
||||
|
||||
WindowEmperor::~WindowEmperor()
|
||||
{
|
||||
_app.Close();
|
||||
_app = nullptr;
|
||||
}
|
||||
|
||||
void _buildArgsFromCommandline(std::vector<winrt::hstring>& args)
|
||||
{
|
||||
if (auto commandline{ GetCommandLineW() })
|
||||
{
|
||||
auto argc = 0;
|
||||
|
||||
// Get the argv, and turn them into a hstring array to pass to the app.
|
||||
wil::unique_any<LPWSTR*, decltype(&::LocalFree), ::LocalFree> argv{ CommandLineToArgvW(commandline, &argc) };
|
||||
if (argv)
|
||||
{
|
||||
for (auto& elem : wil::make_range(argv.get(), argc))
|
||||
{
|
||||
args.emplace_back(elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (args.empty())
|
||||
{
|
||||
args.emplace_back(L"wt.exe");
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowEmperor::HandleCommandlineArgs()
|
||||
{
|
||||
std::vector<winrt::hstring> args;
|
||||
_buildArgsFromCommandline(args);
|
||||
auto cwd{ wil::GetCurrentDirectoryW<std::wstring>() };
|
||||
|
||||
Remoting::CommandlineArgs eventArgs{ { args }, { cwd } };
|
||||
|
||||
const auto isolatedMode{ _app.Logic().IsolatedMode() };
|
||||
|
||||
const auto result = _manager.ProposeCommandline(eventArgs, isolatedMode);
|
||||
|
||||
if (result.ShouldCreateWindow())
|
||||
{
|
||||
_createNewWindowThread(Remoting::WindowRequestedArgs{ result, eventArgs });
|
||||
|
||||
_becomeMonarch();
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto res = _app.Logic().GetParseCommandlineMessage(eventArgs.Commandline());
|
||||
if (!res.Message.empty())
|
||||
{
|
||||
AppHost::s_DisplayMessageBox(res);
|
||||
ExitThread(res.ExitCode);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ShouldCreateWindow();
|
||||
}
|
||||
|
||||
void WindowEmperor::WaitForWindows()
|
||||
{
|
||||
MSG message{};
|
||||
while (GetMessageW(&message, nullptr, 0, 0))
|
||||
{
|
||||
TranslateMessage(&message);
|
||||
DispatchMessage(&message);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowEmperor::_createNewWindowThread(const Remoting::WindowRequestedArgs& args)
|
||||
{
|
||||
Remoting::Peasant peasant{ _manager.CreatePeasant(args) };
|
||||
auto window{ std::make_shared<WindowThread>(_app.Logic(), args, _manager, peasant) };
|
||||
std::weak_ptr<WindowEmperor> weakThis{ weak_from_this() };
|
||||
|
||||
std::thread t([weakThis, window]() {
|
||||
window->CreateHost();
|
||||
|
||||
if (auto self{ weakThis.lock() })
|
||||
{
|
||||
self->_windowStartedHandler(window);
|
||||
}
|
||||
|
||||
window->RunMessagePump();
|
||||
|
||||
if (auto self{ weakThis.lock() })
|
||||
{
|
||||
self->_windowExitedHandler(window->Peasant().GetID());
|
||||
}
|
||||
});
|
||||
LOG_IF_FAILED(SetThreadDescription(t.native_handle(), L"Window Thread"));
|
||||
|
||||
t.detach();
|
||||
}
|
||||
|
||||
// Handler for a WindowThread's Started event, which it raises once the window
|
||||
// thread starts and XAML is ready to go on that thread. Set up some callbacks
|
||||
// now that we know this window is set up and ready to go.
|
||||
// Q: Why isn't adding these callbacks just a part of _createNewWindowThread?
|
||||
// A: Until the thread actually starts, the AppHost (and its Logic()) haven't
|
||||
// been ctor'd or initialized, so trying to add callbacks immediately will A/V
|
||||
void WindowEmperor::_windowStartedHandler(const std::shared_ptr<WindowThread>& sender)
|
||||
{
|
||||
// Add a callback to the window's logic to let us know when the window's
|
||||
// quake mode state changes. We'll use this to check if we need to add
|
||||
// or remove the notification icon.
|
||||
sender->Logic().IsQuakeWindowChanged({ this, &WindowEmperor::_windowIsQuakeWindowChanged });
|
||||
sender->UpdateSettingsRequested({ this, &WindowEmperor::_windowRequestUpdateSettings });
|
||||
|
||||
// Summon the window to the foreground, since we might not _currently_ be in
|
||||
// the foreground, but we should act like the new window is.
|
||||
//
|
||||
// TODO: GH#14957 - use AllowSetForeground from the original wt.exe instead
|
||||
Remoting::SummonWindowSelectionArgs args{};
|
||||
args.OnCurrentDesktop(false);
|
||||
args.WindowID(sender->Peasant().GetID());
|
||||
args.SummonBehavior().MoveToCurrentDesktop(false);
|
||||
args.SummonBehavior().ToggleVisibility(false);
|
||||
args.SummonBehavior().DropdownDuration(0);
|
||||
args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::InPlace);
|
||||
_manager.SummonWindow(args);
|
||||
|
||||
// Now that the window is ready to go, we can add it to our list of windows,
|
||||
// because we know it will be well behaved.
|
||||
//
|
||||
// Be sure to only modify the list of windows under lock.
|
||||
{
|
||||
auto lockedWindows{ _windows.lock() };
|
||||
lockedWindows->push_back(sender);
|
||||
}
|
||||
}
|
||||
void WindowEmperor::_windowExitedHandler(uint64_t senderID)
|
||||
{
|
||||
auto lockedWindows{ _windows.lock() };
|
||||
|
||||
// find the window in _windows who's peasant's Id matches the peasant's Id
|
||||
// and remove it
|
||||
std::erase_if(*lockedWindows,
|
||||
[&](const auto& w) {
|
||||
return w->Peasant().GetID() == senderID;
|
||||
});
|
||||
|
||||
if (lockedWindows->size() == 0)
|
||||
{
|
||||
_close();
|
||||
}
|
||||
}
|
||||
// Method Description:
|
||||
// - Set up all sorts of handlers now that we've determined that we're a process
|
||||
// that will end up hosting the windows. These include:
|
||||
// - Setting up a message window to handle hotkeys and notification icon
|
||||
// invokes.
|
||||
// - Setting up the global hotkeys.
|
||||
// - Setting up the notification icon.
|
||||
// - Setting up callbacks for when the settings change.
|
||||
// - Setting up callbacks for when the number of windows changes.
|
||||
// - Setting up the throttled func for layout persistence. Arguments:
|
||||
// - <none>
|
||||
void WindowEmperor::_becomeMonarch()
|
||||
{
|
||||
// Add a callback to the window manager so that when the Monarch wants a new
|
||||
// window made, they come to us
|
||||
_manager.RequestNewWindow([this](auto&&, const Remoting::WindowRequestedArgs& args) {
|
||||
_createNewWindowThread(args);
|
||||
});
|
||||
|
||||
_createMessageWindow();
|
||||
|
||||
_setupGlobalHotkeys();
|
||||
|
||||
// When the settings change, we'll want to update our global hotkeys and our
|
||||
// notification icon based on the new settings.
|
||||
_app.Logic().SettingsChanged([this](auto&&, const TerminalApp::SettingsLoadEventArgs& args) {
|
||||
if (SUCCEEDED(args.Result()))
|
||||
{
|
||||
_setupGlobalHotkeys();
|
||||
_checkWindowsForNotificationIcon();
|
||||
}
|
||||
});
|
||||
|
||||
// On startup, immediately check if we need to show the notification icon.
|
||||
_checkWindowsForNotificationIcon();
|
||||
|
||||
// Set the number of open windows (so we know if we are the last window)
|
||||
// and subscribe for updates if there are any changes to that number.
|
||||
|
||||
_revokers.WindowCreated = _manager.WindowCreated(winrt::auto_revoke, { this, &WindowEmperor::_numberOfWindowsChanged });
|
||||
_revokers.WindowClosed = _manager.WindowClosed(winrt::auto_revoke, { this, &WindowEmperor::_numberOfWindowsChanged });
|
||||
|
||||
// If the monarch receives a QuitAll event it will signal this event to be
|
||||
// ran before each peasant is closed.
|
||||
_revokers.QuitAllRequested = _manager.QuitAllRequested(winrt::auto_revoke, { this, &WindowEmperor::_quitAllRequested });
|
||||
|
||||
// The monarch should be monitoring if it should save the window layout.
|
||||
// We want at least some delay to prevent the first save from overwriting
|
||||
_getWindowLayoutThrottler.emplace(std::move(std::chrono::seconds(10)), std::move([this]() { _saveWindowLayoutsRepeat(); }));
|
||||
_getWindowLayoutThrottler.value()();
|
||||
|
||||
// BODGY
|
||||
//
|
||||
// We've got a weird crash that happens terribly inconsistently, but pretty
|
||||
// readily on migrie's laptop, only in Debug mode. Apparently, there's some
|
||||
// weird ref-counting magic that goes on during teardown, and our
|
||||
// Application doesn't get closed quite right, which can cause us to crash
|
||||
// into the debugger. This of course, only happens on exit, and happens
|
||||
// somewhere in the XamlHost.dll code.
|
||||
//
|
||||
// Crazily, if we _manually leak the Application_ here, then the crash
|
||||
// doesn't happen. This doesn't matter, because we really want the
|
||||
// Application to live for _the entire lifetime of the process_, so the only
|
||||
// time when this object would actually need to get cleaned up is _during
|
||||
// exit_. So we can safely leak this Application object, and have it just
|
||||
// get cleaned up normally when our process exits.
|
||||
auto a{ _app };
|
||||
::winrt::detach_abi(a);
|
||||
}
|
||||
|
||||
// sender and args are always nullptr
|
||||
void WindowEmperor::_numberOfWindowsChanged(const winrt::Windows::Foundation::IInspectable&,
|
||||
const winrt::Windows::Foundation::IInspectable&)
|
||||
{
|
||||
if (_getWindowLayoutThrottler)
|
||||
{
|
||||
_getWindowLayoutThrottler.value()();
|
||||
}
|
||||
|
||||
// If we closed out the quake window, and don't otherwise need the tray
|
||||
// icon, let's get rid of it.
|
||||
_checkWindowsForNotificationIcon();
|
||||
}
|
||||
|
||||
// Raised from our windowManager (on behalf of the monarch). We respond by
|
||||
// giving the monarch an async function that the manager should wait on before
|
||||
// completing the quit.
|
||||
void WindowEmperor::_quitAllRequested(const winrt::Windows::Foundation::IInspectable&,
|
||||
const winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs& args)
|
||||
{
|
||||
// Make sure that the current timer is destroyed so that it doesn't attempt
|
||||
// to run while we are in the middle of quitting.
|
||||
if (_getWindowLayoutThrottler.has_value())
|
||||
{
|
||||
_getWindowLayoutThrottler.reset();
|
||||
}
|
||||
|
||||
// Tell the monarch to wait for the window layouts to save before
|
||||
// everyone quits.
|
||||
args.BeforeQuitAllAction(_saveWindowLayouts());
|
||||
}
|
||||
|
||||
#pragma region LayoutPersistence
|
||||
|
||||
winrt::Windows::Foundation::IAsyncAction WindowEmperor::_saveWindowLayouts()
|
||||
{
|
||||
// Make sure we run on a background thread to not block anything.
|
||||
co_await winrt::resume_background();
|
||||
|
||||
if (_app.Logic().ShouldUsePersistedLayout())
|
||||
{
|
||||
try
|
||||
{
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_SaveWindowLayouts_Collect",
|
||||
TraceLoggingDescription("Logged when collecting window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
const auto layoutJsons = _manager.GetAllWindowLayouts();
|
||||
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_SaveWindowLayouts_Save",
|
||||
TraceLoggingDescription("Logged when writing window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
_app.Logic().SaveWindowLayoutJsons(layoutJsons);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_SaveWindowLayouts_Failed",
|
||||
TraceLoggingDescription("An error occurred when collecting or writing window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
}
|
||||
}
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
winrt::fire_and_forget WindowEmperor::_saveWindowLayoutsRepeat()
|
||||
{
|
||||
// Make sure we run on a background thread to not block anything.
|
||||
co_await winrt::resume_background();
|
||||
|
||||
co_await _saveWindowLayouts();
|
||||
|
||||
// Don't need to save too frequently.
|
||||
co_await winrt::resume_after(30s);
|
||||
|
||||
// As long as we are supposed to keep saving, request another save.
|
||||
// This will be delayed by the throttler so that at most one save happens
|
||||
// per 10 seconds, if a save is requested by another source simultaneously.
|
||||
if (_getWindowLayoutThrottler.has_value())
|
||||
{
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_requestGetLayout",
|
||||
TraceLoggingDescription("Logged when triggering a throttled write of the window state"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
_getWindowLayoutThrottler.value()();
|
||||
}
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
#pragma region WindowProc
|
||||
|
||||
static WindowEmperor* GetThisFromHandle(HWND const window) noexcept
|
||||
{
|
||||
const auto data = GetWindowLongPtr(window, GWLP_USERDATA);
|
||||
return reinterpret_cast<WindowEmperor*>(data);
|
||||
}
|
||||
[[nodiscard]] LRESULT __stdcall WindowEmperor::_wndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
|
||||
{
|
||||
WINRT_ASSERT(window);
|
||||
|
||||
if (WM_NCCREATE == message)
|
||||
{
|
||||
auto cs = reinterpret_cast<CREATESTRUCT*>(lparam);
|
||||
WindowEmperor* that = static_cast<WindowEmperor*>(cs->lpCreateParams);
|
||||
WINRT_ASSERT(that);
|
||||
WINRT_ASSERT(!that->_window);
|
||||
that->_window = wil::unique_hwnd(window);
|
||||
SetWindowLongPtr(that->_window.get(), GWLP_USERDATA, reinterpret_cast<LONG_PTR>(that));
|
||||
}
|
||||
else if (WindowEmperor* that = GetThisFromHandle(window))
|
||||
{
|
||||
return that->_messageHandler(message, wparam, lparam);
|
||||
}
|
||||
|
||||
return DefWindowProc(window, message, wparam, lparam);
|
||||
}
|
||||
void WindowEmperor::_createMessageWindow()
|
||||
{
|
||||
WNDCLASS wc{};
|
||||
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
wc.hInstance = reinterpret_cast<HINSTANCE>(&__ImageBase);
|
||||
wc.lpszClassName = TERMINAL_MESSAGE_CLASS_NAME;
|
||||
wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wc.lpfnWndProc = WindowEmperor::_wndProc;
|
||||
wc.hIcon = LoadIconW(wc.hInstance, MAKEINTRESOURCEW(IDI_APPICON));
|
||||
RegisterClass(&wc);
|
||||
WINRT_ASSERT(!_window);
|
||||
|
||||
WINRT_VERIFY(CreateWindow(wc.lpszClassName,
|
||||
L"Windows Terminal",
|
||||
0,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
HWND_MESSAGE,
|
||||
nullptr,
|
||||
wc.hInstance,
|
||||
this));
|
||||
}
|
||||
|
||||
LRESULT WindowEmperor::_messageHandler(UINT const message, WPARAM const wParam, LPARAM const lParam) noexcept
|
||||
{
|
||||
// use C++11 magic statics to make sure we only do this once.
|
||||
// This won't change over the lifetime of the application
|
||||
static const UINT WM_TASKBARCREATED = []() { return RegisterWindowMessageW(L"TaskbarCreated"); }();
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case WM_HOTKEY:
|
||||
{
|
||||
_hotkeyPressed(static_cast<long>(wParam));
|
||||
return 0;
|
||||
}
|
||||
case CM_NOTIFY_FROM_NOTIFICATION_AREA:
|
||||
{
|
||||
switch (LOWORD(lParam))
|
||||
{
|
||||
case NIN_SELECT:
|
||||
case NIN_KEYSELECT:
|
||||
{
|
||||
_notificationIcon->NotificationIconPressed();
|
||||
return 0;
|
||||
}
|
||||
case WM_CONTEXTMENU:
|
||||
{
|
||||
const til::point eventPoint{ GET_X_LPARAM(wParam), GET_Y_LPARAM(wParam) };
|
||||
_notificationIcon->ShowContextMenu(eventPoint, _manager.GetPeasantInfos());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WM_MENUCOMMAND:
|
||||
{
|
||||
_notificationIcon->MenuItemSelected((HMENU)lParam, (UINT)wParam);
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
{
|
||||
// We'll want to receive this message when explorer.exe restarts
|
||||
// so that we can re-add our icon to the notification area.
|
||||
// This unfortunately isn't a switch case because we register the
|
||||
// message at runtime.
|
||||
if (message == WM_TASKBARCREATED)
|
||||
{
|
||||
_notificationIcon->ReAddNotificationIcon();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return DefWindowProc(_window.get(), message, wParam, lParam);
|
||||
}
|
||||
|
||||
winrt::fire_and_forget WindowEmperor::_close()
|
||||
{
|
||||
// Important! Switch back to the main thread for the emperor. That way, the
|
||||
// quit will go to the emperor's message pump.
|
||||
co_await wil::resume_foreground(_dispatcher);
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
#pragma region GlobalHotkeys
|
||||
|
||||
// Method Description:
|
||||
// - Called when the monarch failed to summon a window for a given set of
|
||||
// SummonWindowSelectionArgs. In this case, we should create the specified
|
||||
// window ourselves.
|
||||
// - This is to support the scenario like `globalSummon(Name="_quake")` being
|
||||
// used to summon the window if it already exists, or create it if it doesn't.
|
||||
// Arguments:
|
||||
// - args: Contains information on how we should name the window
|
||||
// Return Value:
|
||||
// - <none>
|
||||
static winrt::fire_and_forget _createNewTerminalWindow(Settings::Model::GlobalSummonArgs args)
|
||||
{
|
||||
// Hop to the BG thread
|
||||
co_await winrt::resume_background();
|
||||
|
||||
// This will get us the correct exe for dev/preview/release. If you
|
||||
// don't stick this in a local, it'll get mangled by ShellExecute. I
|
||||
// have no idea why.
|
||||
const auto exePath{ GetWtExePath() };
|
||||
|
||||
// If we weren't given a name, then just use new to force the window to be
|
||||
// unnamed.
|
||||
winrt::hstring cmdline{
|
||||
fmt::format(L"-w {}",
|
||||
args.Name().empty() ? L"new" :
|
||||
args.Name())
|
||||
};
|
||||
|
||||
SHELLEXECUTEINFOW seInfo{ 0 };
|
||||
seInfo.cbSize = sizeof(seInfo);
|
||||
seInfo.fMask = SEE_MASK_NOASYNC;
|
||||
seInfo.lpVerb = L"open";
|
||||
seInfo.lpFile = exePath.c_str();
|
||||
seInfo.lpParameters = cmdline.c_str();
|
||||
seInfo.nShow = SW_SHOWNORMAL;
|
||||
LOG_IF_WIN32_BOOL_FALSE(ShellExecuteExW(&seInfo));
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
void WindowEmperor::_hotkeyPressed(const long hotkeyIndex)
|
||||
{
|
||||
if (hotkeyIndex < 0 || static_cast<size_t>(hotkeyIndex) > _hotkeys.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& summonArgs = til::at(_hotkeys, hotkeyIndex);
|
||||
Remoting::SummonWindowSelectionArgs args{ summonArgs.Name() };
|
||||
|
||||
// desktop:any - MoveToCurrentDesktop=false, OnCurrentDesktop=false
|
||||
// desktop:toCurrent - MoveToCurrentDesktop=true, OnCurrentDesktop=false
|
||||
// desktop:onCurrent - MoveToCurrentDesktop=false, OnCurrentDesktop=true
|
||||
args.OnCurrentDesktop(summonArgs.Desktop() == Settings::Model::DesktopBehavior::OnCurrent);
|
||||
args.SummonBehavior().MoveToCurrentDesktop(summonArgs.Desktop() == Settings::Model::DesktopBehavior::ToCurrent);
|
||||
args.SummonBehavior().ToggleVisibility(summonArgs.ToggleVisibility());
|
||||
args.SummonBehavior().DropdownDuration(summonArgs.DropdownDuration());
|
||||
|
||||
switch (summonArgs.Monitor())
|
||||
{
|
||||
case Settings::Model::MonitorBehavior::Any:
|
||||
args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::InPlace);
|
||||
break;
|
||||
case Settings::Model::MonitorBehavior::ToCurrent:
|
||||
args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::ToCurrent);
|
||||
break;
|
||||
case Settings::Model::MonitorBehavior::ToMouse:
|
||||
args.SummonBehavior().ToMonitor(Remoting::MonitorBehavior::ToMouse);
|
||||
break;
|
||||
}
|
||||
|
||||
_manager.SummonWindow(args);
|
||||
if (args.FoundMatch())
|
||||
{
|
||||
// Excellent, the window was found. We have nothing else to do here.
|
||||
}
|
||||
else
|
||||
{
|
||||
// We should make the window ourselves.
|
||||
_createNewTerminalWindow(summonArgs);
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowEmperor::_registerHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept
|
||||
{
|
||||
const auto vkey = hotkey.Vkey();
|
||||
auto hotkeyFlags = MOD_NOREPEAT;
|
||||
{
|
||||
const auto modifiers = hotkey.Modifiers();
|
||||
WI_SetFlagIf(hotkeyFlags, MOD_WIN, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Windows));
|
||||
WI_SetFlagIf(hotkeyFlags, MOD_ALT, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Menu));
|
||||
WI_SetFlagIf(hotkeyFlags, MOD_CONTROL, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Control));
|
||||
WI_SetFlagIf(hotkeyFlags, MOD_SHIFT, WI_IsFlagSet(modifiers, VirtualKeyModifiers::Shift));
|
||||
}
|
||||
|
||||
// TODO GH#8888: We should display a warning of some kind if this fails.
|
||||
// This can fail if something else already bound this hotkey.
|
||||
const auto result = ::RegisterHotKey(_window.get(), index, hotkeyFlags, vkey);
|
||||
LOG_LAST_ERROR_IF(!result);
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"RegisterHotKey",
|
||||
TraceLoggingDescription("Emitted when setting hotkeys"),
|
||||
TraceLoggingInt64(index, "index", "the index of the hotkey to add"),
|
||||
TraceLoggingUInt64(vkey, "vkey", "the key"),
|
||||
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_WIN), "win", "is WIN in the modifiers"),
|
||||
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_ALT), "alt", "is ALT in the modifiers"),
|
||||
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_CONTROL), "control", "is CONTROL in the modifiers"),
|
||||
TraceLoggingUInt64(WI_IsFlagSet(hotkeyFlags, MOD_SHIFT), "shift", "is SHIFT in the modifiers"),
|
||||
TraceLoggingBool(result, "succeeded", "true if we succeeded"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Call UnregisterHotKey once for each previously registered hotkey.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void WindowEmperor::_unregisterHotKey(const int index) noexcept
|
||||
{
|
||||
TraceLoggingWrite(
|
||||
g_hWindowsTerminalProvider,
|
||||
"UnregisterHotKey",
|
||||
TraceLoggingDescription("Emitted when clearing previously set hotkeys"),
|
||||
TraceLoggingInt64(index, "index", "the index of the hotkey to remove"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
|
||||
LOG_IF_WIN32_BOOL_FALSE(::UnregisterHotKey(_window.get(), index));
|
||||
}
|
||||
|
||||
winrt::fire_and_forget WindowEmperor::_setupGlobalHotkeys()
|
||||
{
|
||||
// The hotkey MUST be registered on the main thread. It will fail otherwise!
|
||||
co_await wil::resume_foreground(_dispatcher);
|
||||
|
||||
if (!_window)
|
||||
{
|
||||
// MSFT:36797001 There's a surprising number of hits of this callback
|
||||
// getting triggered during teardown. As a best practice, we really
|
||||
// should make sure _window exists before accessing it on any coroutine.
|
||||
// We might be getting called back after the app already began getting
|
||||
// cleaned up.
|
||||
co_return;
|
||||
}
|
||||
// Unregister all previously registered hotkeys.
|
||||
//
|
||||
// RegisterHotKey(), will not unregister hotkeys automatically.
|
||||
// If a hotkey with a given HWND and ID combination already exists
|
||||
// then a duplicate one will be added, which we don't want.
|
||||
// (Additionally we want to remove hotkeys that were removed from the settings.)
|
||||
for (auto i = 0, count = gsl::narrow_cast<int>(_hotkeys.size()); i < count; ++i)
|
||||
{
|
||||
_unregisterHotKey(i);
|
||||
}
|
||||
|
||||
_hotkeys.clear();
|
||||
|
||||
// Re-register all current hotkeys.
|
||||
for (const auto& [keyChord, cmd] : _app.Logic().GlobalHotkeys())
|
||||
{
|
||||
if (auto summonArgs = cmd.ActionAndArgs().Args().try_as<Settings::Model::GlobalSummonArgs>())
|
||||
{
|
||||
auto index = gsl::narrow_cast<int>(_hotkeys.size());
|
||||
const auto succeeded = _registerHotKey(index, keyChord);
|
||||
|
||||
TraceLoggingWrite(g_hWindowsTerminalProvider,
|
||||
"AppHost_setupGlobalHotkey",
|
||||
TraceLoggingDescription("Emitted when setting a single hotkey"),
|
||||
TraceLoggingInt64(index, "index", "the index of the hotkey to add"),
|
||||
TraceLoggingWideString(cmd.Name().c_str(), "name", "the name of the command"),
|
||||
TraceLoggingBoolean(succeeded, "succeeded", "true if we succeeded"),
|
||||
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
|
||||
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
|
||||
_hotkeys.emplace_back(summonArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region NotificationIcon
|
||||
// Method Description:
|
||||
// - Creates a Notification Icon and hooks up its handlers
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void WindowEmperor::_createNotificationIcon()
|
||||
{
|
||||
_notificationIcon = std::make_unique<NotificationIcon>(_window.get());
|
||||
_notificationIcon->SummonWindowRequested([this](auto& args) { _manager.SummonWindow(args); });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Deletes our notification icon if we have one.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void WindowEmperor::_destroyNotificationIcon()
|
||||
{
|
||||
_notificationIcon->RemoveIconFromNotificationArea();
|
||||
_notificationIcon = nullptr;
|
||||
}
|
||||
|
||||
void WindowEmperor::_checkWindowsForNotificationIcon()
|
||||
{
|
||||
// We need to check some conditions to show the notification icon.
|
||||
//
|
||||
// * If there's a Quake window somewhere, we'll want to keep the
|
||||
// notification icon.
|
||||
// * There's two settings - MinimizeToNotificationArea and
|
||||
// AlwaysShowNotificationIcon. If either one of them are true, we want to
|
||||
// make sure there's a notification icon.
|
||||
//
|
||||
// If both are false, we want to remove our icon from the notification area.
|
||||
// When we remove our icon from the notification area, we'll also want to
|
||||
// re-summon any hidden windows, but right now we're not keeping track of
|
||||
// who's hidden, so just summon them all. Tracking the work to do a "summon
|
||||
// all minimized" in GH#10448
|
||||
//
|
||||
// To avoid races between us thinking the settings updated, and the windows
|
||||
// themselves getting the new settings, only ask the app logic for the
|
||||
// RequestsTrayIcon setting value, and combine that with the result of each
|
||||
// window (which won't change during a settings reload).
|
||||
bool needsIcon = _app.Logic().RequestsTrayIcon();
|
||||
{
|
||||
auto windows{ _windows.lock_shared() };
|
||||
for (const auto& _windowThread : *windows)
|
||||
{
|
||||
needsIcon |= _windowThread->Logic().IsQuakeWindow();
|
||||
}
|
||||
}
|
||||
|
||||
if (needsIcon)
|
||||
{
|
||||
_showNotificationIconRequested();
|
||||
}
|
||||
else
|
||||
{
|
||||
_hideNotificationIconRequested();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowEmperor::_showNotificationIconRequested()
|
||||
{
|
||||
if (!_notificationIcon)
|
||||
{
|
||||
_createNotificationIcon();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowEmperor::_hideNotificationIconRequested()
|
||||
{
|
||||
// Destroy it only if our settings allow it
|
||||
if (_notificationIcon)
|
||||
{
|
||||
// If we no longer want the tray icon, but we did have one, then quick
|
||||
// re-summon all our windows, so they don't get lost when the icon
|
||||
// disappears forever.
|
||||
_manager.SummonAllWindows();
|
||||
|
||||
_destroyNotificationIcon();
|
||||
}
|
||||
}
|
||||
#pragma endregion
|
||||
|
||||
// A callback to the window's logic to let us know when the window's
|
||||
// quake mode state changes. We'll use this to check if we need to add
|
||||
// or remove the notification icon.
|
||||
winrt::fire_and_forget WindowEmperor::_windowIsQuakeWindowChanged(winrt::Windows::Foundation::IInspectable sender,
|
||||
winrt::Windows::Foundation::IInspectable args)
|
||||
{
|
||||
co_await wil::resume_foreground(this->_dispatcher);
|
||||
_checkWindowsForNotificationIcon();
|
||||
}
|
||||
winrt::fire_and_forget WindowEmperor::_windowRequestUpdateSettings()
|
||||
{
|
||||
// We MUST be on the main thread to update the settings. We will crash when trying to enumerate fragment extensions otherwise.
|
||||
co_await wil::resume_foreground(this->_dispatcher);
|
||||
_app.Logic().ReloadSettings();
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation Licensed under the MIT license.
|
||||
|
||||
Class Name:
|
||||
- WindowEmperor.h
|
||||
|
||||
Abstract:
|
||||
- The WindowEmperor is our class for managing the single Terminal process
|
||||
with all our windows. It will be responsible for handling the commandline
|
||||
arguments. It will initially try to find another terminal process to
|
||||
communicate with. If it does, it'll hand off to the existing process.
|
||||
- If it determines that it should create a window, it will set up a new thread
|
||||
for that window, and a message loop on the main thread for handling global
|
||||
state, such as hotkeys and the notification icon.
|
||||
|
||||
--*/
|
||||
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
|
||||
#include "WindowThread.h"
|
||||
|
||||
class WindowEmperor : public std::enable_shared_from_this<WindowEmperor>
|
||||
{
|
||||
public:
|
||||
WindowEmperor() noexcept;
|
||||
~WindowEmperor();
|
||||
void WaitForWindows();
|
||||
|
||||
bool HandleCommandlineArgs();
|
||||
|
||||
private:
|
||||
void _createNewWindowThread(const winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs& args);
|
||||
|
||||
[[nodiscard]] static LRESULT __stdcall _wndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept;
|
||||
LRESULT _messageHandler(UINT const message, WPARAM const wParam, LPARAM const lParam) noexcept;
|
||||
wil::unique_hwnd _window;
|
||||
|
||||
winrt::TerminalApp::App _app;
|
||||
winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr };
|
||||
winrt::Microsoft::Terminal::Remoting::WindowManager _manager;
|
||||
|
||||
til::shared_mutex<std::vector<std::shared_ptr<WindowThread>>> _windows;
|
||||
|
||||
std::optional<til::throttled_func_trailing<>> _getWindowLayoutThrottler;
|
||||
|
||||
winrt::event_token _WindowCreatedToken;
|
||||
winrt::event_token _WindowClosedToken;
|
||||
|
||||
std::vector<winrt::Microsoft::Terminal::Settings::Model::GlobalSummonArgs> _hotkeys;
|
||||
|
||||
std::unique_ptr<NotificationIcon> _notificationIcon;
|
||||
|
||||
void _windowStartedHandler(const std::shared_ptr<WindowThread>& sender);
|
||||
void _windowExitedHandler(uint64_t senderID);
|
||||
|
||||
void _becomeMonarch();
|
||||
void _numberOfWindowsChanged(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&);
|
||||
void _quitAllRequested(const winrt::Windows::Foundation::IInspectable&,
|
||||
const winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs&);
|
||||
|
||||
winrt::fire_and_forget _windowIsQuakeWindowChanged(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::Foundation::IInspectable args);
|
||||
winrt::fire_and_forget _windowRequestUpdateSettings();
|
||||
|
||||
winrt::Windows::Foundation::IAsyncAction _saveWindowLayouts();
|
||||
winrt::fire_and_forget _saveWindowLayoutsRepeat();
|
||||
|
||||
void _createMessageWindow();
|
||||
|
||||
void _hotkeyPressed(const long hotkeyIndex);
|
||||
bool _registerHotKey(const int index, const winrt::Microsoft::Terminal::Control::KeyChord& hotkey) noexcept;
|
||||
void _unregisterHotKey(const int index) noexcept;
|
||||
winrt::fire_and_forget _setupGlobalHotkeys();
|
||||
|
||||
winrt::fire_and_forget _close();
|
||||
|
||||
void _createNotificationIcon();
|
||||
void _destroyNotificationIcon();
|
||||
void _checkWindowsForNotificationIcon();
|
||||
void _showNotificationIconRequested();
|
||||
void _hideNotificationIconRequested();
|
||||
|
||||
struct Revokers
|
||||
{
|
||||
winrt::Microsoft::Terminal::Remoting::WindowManager::WindowCreated_revoker WindowCreated;
|
||||
winrt::Microsoft::Terminal::Remoting::WindowManager::WindowClosed_revoker WindowClosed;
|
||||
winrt::Microsoft::Terminal::Remoting::WindowManager::QuitAllRequested_revoker QuitAllRequested;
|
||||
} _revokers{};
|
||||
};
|
|
@ -0,0 +1,132 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "WindowThread.h"
|
||||
|
||||
WindowThread::WindowThread(winrt::TerminalApp::AppLogic logic,
|
||||
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
|
||||
winrt::Microsoft::Terminal::Remoting::WindowManager manager,
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant peasant) :
|
||||
_peasant{ std::move(peasant) },
|
||||
_appLogic{ std::move(logic) },
|
||||
_args{ std::move(args) },
|
||||
_manager{ std::move(manager) }
|
||||
{
|
||||
// DO NOT start the AppHost here in the ctor, as that will start XAML on the wrong thread!
|
||||
}
|
||||
|
||||
void WindowThread::CreateHost()
|
||||
{
|
||||
// Start the AppHost HERE, on the actual thread we want XAML to run on
|
||||
_host = std::make_unique<::AppHost>(_appLogic,
|
||||
_args,
|
||||
_manager,
|
||||
_peasant);
|
||||
_host->UpdateSettingsRequested([this]() { _UpdateSettingsRequestedHandlers(); });
|
||||
|
||||
winrt::init_apartment(winrt::apartment_type::single_threaded);
|
||||
|
||||
// Initialize the xaml content. This must be called AFTER the
|
||||
// WindowsXamlManager is initialized.
|
||||
_host->Initialize();
|
||||
}
|
||||
|
||||
int WindowThread::RunMessagePump()
|
||||
{
|
||||
// Enter the main window loop.
|
||||
const auto exitCode = _messagePump();
|
||||
// Here, the main window loop has exited.
|
||||
|
||||
_host = nullptr;
|
||||
// !! LOAD BEARING !!
|
||||
//
|
||||
// Make sure to finish pumping all the messages for our thread here. We
|
||||
// may think we're all done, but we're not quite. XAML needs more time
|
||||
// to pump the remaining events through, even at the point we're
|
||||
// exiting. So do that now. If you don't, then the last tab to close
|
||||
// will never actually destruct the last tab / TermControl / ControlCore
|
||||
// / renderer.
|
||||
{
|
||||
MSG msg = {};
|
||||
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE))
|
||||
{
|
||||
::DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
winrt::TerminalApp::TerminalWindow WindowThread::Logic()
|
||||
{
|
||||
return _host->Logic();
|
||||
}
|
||||
|
||||
static bool _messageIsF7Keypress(const MSG& message)
|
||||
{
|
||||
return (message.message == WM_KEYDOWN || message.message == WM_SYSKEYDOWN) && message.wParam == VK_F7;
|
||||
}
|
||||
static bool _messageIsAltKeyup(const MSG& message)
|
||||
{
|
||||
return (message.message == WM_KEYUP || message.message == WM_SYSKEYUP) && message.wParam == VK_MENU;
|
||||
}
|
||||
static bool _messageIsAltSpaceKeypress(const MSG& message)
|
||||
{
|
||||
return message.message == WM_SYSKEYDOWN && message.wParam == VK_SPACE;
|
||||
}
|
||||
|
||||
int WindowThread::_messagePump()
|
||||
{
|
||||
MSG message{};
|
||||
|
||||
while (GetMessageW(&message, nullptr, 0, 0))
|
||||
{
|
||||
// GH#638 (Pressing F7 brings up both the history AND a caret browsing message)
|
||||
// The Xaml input stack doesn't allow an application to suppress the "caret browsing"
|
||||
// dialog experience triggered when you press F7. Official recommendation from the Xaml
|
||||
// team is to catch F7 before we hand it off.
|
||||
// AppLogic contains an ad-hoc implementation of event bubbling for a runtime classes
|
||||
// implementing a custom IF7Listener interface.
|
||||
// If the recipient of IF7Listener::OnF7Pressed suggests that the F7 press has, in fact,
|
||||
// been handled we can discard the message before we even translate it.
|
||||
if (_messageIsF7Keypress(message))
|
||||
{
|
||||
if (_host->OnDirectKeyEvent(VK_F7, LOBYTE(HIWORD(message.lParam)), true))
|
||||
{
|
||||
// The application consumed the F7. Don't let Xaml get it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// GH#6421 - System XAML will never send an Alt KeyUp event. So, similar
|
||||
// to how we'll steal the F7 KeyDown above, we'll steal the Alt KeyUp
|
||||
// here, and plumb it through.
|
||||
if (_messageIsAltKeyup(message))
|
||||
{
|
||||
// Let's pass <Alt> to the application
|
||||
if (_host->OnDirectKeyEvent(VK_MENU, LOBYTE(HIWORD(message.lParam)), false))
|
||||
{
|
||||
// The application consumed the Alt. Don't let Xaml get it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// GH#7125 = System XAML will show a system dialog on Alt Space. We want to
|
||||
// explicitly prevent that because we handle that ourselves. So similar to
|
||||
// above, we steal the event and hand it off to the host.
|
||||
if (_messageIsAltSpaceKeypress(message))
|
||||
{
|
||||
_host->OnDirectKeyEvent(VK_SPACE, LOBYTE(HIWORD(message.lParam)), true);
|
||||
continue;
|
||||
}
|
||||
|
||||
TranslateMessage(&message);
|
||||
DispatchMessage(&message);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant WindowThread::Peasant()
|
||||
{
|
||||
return _peasant;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
#pragma once
|
||||
#include "pch.h"
|
||||
#include "AppHost.h"
|
||||
|
||||
class WindowThread : public std::enable_shared_from_this<WindowThread>
|
||||
{
|
||||
public:
|
||||
WindowThread(winrt::TerminalApp::AppLogic logic,
|
||||
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
|
||||
winrt::Microsoft::Terminal::Remoting::WindowManager manager,
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant peasant);
|
||||
|
||||
winrt::TerminalApp::TerminalWindow Logic();
|
||||
void CreateHost();
|
||||
int RunMessagePump();
|
||||
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant Peasant();
|
||||
|
||||
WINRT_CALLBACK(UpdateSettingsRequested, winrt::delegate<void()>);
|
||||
|
||||
private:
|
||||
winrt::Microsoft::Terminal::Remoting::Peasant _peasant{ nullptr };
|
||||
|
||||
winrt::TerminalApp::AppLogic _appLogic{ nullptr };
|
||||
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs _args{ nullptr };
|
||||
winrt::Microsoft::Terminal::Remoting::WindowManager _manager{ nullptr };
|
||||
|
||||
std::unique_ptr<::AppHost> _host{ nullptr };
|
||||
|
||||
int _messagePump();
|
||||
};
|
|
@ -56,6 +56,8 @@
|
|||
<ClInclude Include="NonClientIslandWindow.h" />
|
||||
<ClInclude Include="NotificationIcon.h" />
|
||||
<ClInclude Include="VirtualDesktopUtils.h" />
|
||||
<ClInclude Include="WindowEmperor.h" />
|
||||
<ClInclude Include="WindowThread.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="pch.cpp">
|
||||
|
@ -67,6 +69,8 @@
|
|||
<ClCompile Include="NonClientIslandWindow.cpp" />
|
||||
<ClCompile Include="NotificationIcon.cpp" />
|
||||
<ClCompile Include="VirtualDesktopUtils.cpp" />
|
||||
<ClCompile Include="WindowEmperor.cpp" />
|
||||
<ClCompile Include="WindowThread.cpp" />
|
||||
<ClCompile Include="icon.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "AppHost.h"
|
||||
#include "WindowEmperor.h"
|
||||
#include "resource.h"
|
||||
#include "../types/inc/User32Utils.hpp"
|
||||
#include <WilErrorReporting.h>
|
||||
|
@ -83,19 +83,6 @@ static void EnsureNativeArchitecture()
|
|||
}
|
||||
}
|
||||
|
||||
static bool _messageIsF7Keypress(const MSG& message)
|
||||
{
|
||||
return (message.message == WM_KEYDOWN || message.message == WM_SYSKEYDOWN) && message.wParam == VK_F7;
|
||||
}
|
||||
static bool _messageIsAltKeyup(const MSG& message)
|
||||
{
|
||||
return (message.message == WM_KEYUP || message.message == WM_SYSKEYUP) && message.wParam == VK_MENU;
|
||||
}
|
||||
static bool _messageIsAltSpaceKeypress(const MSG& message)
|
||||
{
|
||||
return message.message == WM_SYSKEYDOWN && message.wParam == VK_SPACE;
|
||||
}
|
||||
|
||||
int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
|
||||
{
|
||||
TraceLoggingRegister(g_hWindowsTerminalProvider);
|
||||
|
@ -127,68 +114,9 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int)
|
|||
// doing that, we can safely init as STA before any WinRT dispatches.
|
||||
winrt::init_apartment(winrt::apartment_type::single_threaded);
|
||||
|
||||
// Create the AppHost object, which will create both the window and the
|
||||
// Terminal App. This MUST BE constructed before the Xaml manager as TermApp
|
||||
// provides an implementation of Windows.UI.Xaml.Application.
|
||||
AppHost host;
|
||||
if (!host.HasWindow())
|
||||
const auto emperor = std::make_shared<::WindowEmperor>();
|
||||
if (emperor->HandleCommandlineArgs())
|
||||
{
|
||||
// If we were told to not have a window, exit early. Make sure to use
|
||||
// ExitProcess to die here. If you try just `return 0`, then
|
||||
// the XAML app host will crash during teardown. ExitProcess avoids
|
||||
// that.
|
||||
ExitProcess(0);
|
||||
emperor->WaitForWindows();
|
||||
}
|
||||
|
||||
// Initialize the xaml content. This must be called AFTER the
|
||||
// WindowsXamlManager is initialized.
|
||||
host.Initialize();
|
||||
|
||||
MSG message;
|
||||
|
||||
while (GetMessage(&message, nullptr, 0, 0))
|
||||
{
|
||||
// GH#638 (Pressing F7 brings up both the history AND a caret browsing message)
|
||||
// The Xaml input stack doesn't allow an application to suppress the "caret browsing"
|
||||
// dialog experience triggered when you press F7. Official recommendation from the Xaml
|
||||
// team is to catch F7 before we hand it off.
|
||||
// AppLogic contains an ad-hoc implementation of event bubbling for a runtime classes
|
||||
// implementing a custom IF7Listener interface.
|
||||
// If the recipient of IF7Listener::OnF7Pressed suggests that the F7 press has, in fact,
|
||||
// been handled we can discard the message before we even translate it.
|
||||
if (_messageIsF7Keypress(message))
|
||||
{
|
||||
if (host.OnDirectKeyEvent(VK_F7, LOBYTE(HIWORD(message.lParam)), true))
|
||||
{
|
||||
// The application consumed the F7. Don't let Xaml get it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// GH#6421 - System XAML will never send an Alt KeyUp event. So, similar
|
||||
// to how we'll steal the F7 KeyDown above, we'll steal the Alt KeyUp
|
||||
// here, and plumb it through.
|
||||
if (_messageIsAltKeyup(message))
|
||||
{
|
||||
// Let's pass <Alt> to the application
|
||||
if (host.OnDirectKeyEvent(VK_MENU, LOBYTE(HIWORD(message.lParam)), false))
|
||||
{
|
||||
// The application consumed the Alt. Don't let Xaml get it.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// GH#7125 = System XAML will show a system dialog on Alt Space. We want to
|
||||
// explicitly prevent that because we handle that ourselves. So similar to
|
||||
// above, we steal the event and hand it off to the host.
|
||||
if (_messageIsAltSpaceKeypress(message))
|
||||
{
|
||||
host.OnDirectKeyEvent(VK_SPACE, LOBYTE(HIWORD(message.lParam)), true);
|
||||
continue;
|
||||
}
|
||||
|
||||
TranslateMessage(&message);
|
||||
DispatchMessage(&message);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -87,7 +87,9 @@ TRACELOGGING_DECLARE_PROVIDER(g_hWindowsTerminalProvider);
|
|||
#include <shellapi.h>
|
||||
#include <processenv.h>
|
||||
#include <WinUser.h>
|
||||
|
||||
#include "til.h"
|
||||
#include "til/mutex.h"
|
||||
|
||||
#include <cppwinrt_utils.h>
|
||||
#include <wil/cppwinrt_helpers.h> // must go after the CoreDispatcher type is defined
|
||||
|
|
|
@ -9,5 +9,6 @@ constexpr int32_t WindowingBehaviorUseNew{ -1 };
|
|||
constexpr int32_t WindowingBehaviorUseExisting{ -2 };
|
||||
constexpr int32_t WindowingBehaviorUseAnyExisting{ -3 };
|
||||
constexpr int32_t WindowingBehaviorUseName{ -4 };
|
||||
constexpr int32_t WindowingBehaviorUseNone{ -5 };
|
||||
|
||||
static constexpr std::wstring_view QuakeWindowName{ L"_quake" };
|
||||
|
|
Загрузка…
Ссылка в новой задаче