Migrate OSS up to e5180fe88

This commit is contained in:
Dustin Howett 2021-11-09 13:39:09 -06:00
commit 87182554c8
143 changed files with 4113 additions and 1276 deletions

View File

@ -1,6 +1,7 @@
root = true
[*]
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

2
.gitattributes vendored
View File

@ -3,6 +3,8 @@
###############################################################################
* -text
*.inc linguist-language=cpp
###############################################################################
# Set default behavior for command prompt diff.
#

View File

@ -1,6 +1,7 @@
apc
calt
ccmp
changelog
cybersecurity
Apc
clickable
@ -41,6 +42,7 @@ Lorigin
maxed
mkmk
mru
noreply
nje
ogonek
ok'd

View File

@ -25,6 +25,7 @@ DERR
dlldata
DONTADDTORECENT
DWORDLONG
enumset
environstrings
EXPCMDFLAGS
EXPCMDSTATE

View File

@ -24,7 +24,9 @@ DTDs
DWINRT
enablewttlogging
Intelli
IVisual
LKG
LOCKFILE
Lxss
mfcribbon
microsoft

View File

@ -53,6 +53,7 @@ oldnewthing
opengl
osgwiki
pabhojwa
panos
paulcam
pauldotknopf
PGP

View File

@ -194,6 +194,8 @@ cascadia
cassert
castsi
catid
carlos
zamora
cazamor
CBash
cbegin
@ -2690,6 +2692,7 @@ WINDOWPOSCHANGING
windowproc
windowrect
windowsapp
windowsdeveloper
windowsinternalstring
WINDOWSIZE
windowsx

View File

@ -24,3 +24,4 @@ VERIFY_ARE_EQUAL\(L"[^"]+"
std::memory_order_[\w]+
D2DERR_SHADER_COMPILE_FAILED
TIL_FEATURE_[0-9A-Z_]+
vcvars\w*

View File

@ -15,7 +15,7 @@ Import-Module .\tools\OpenConsole.psm1
Set-MsBuildDevEnvironment
Get-Format
```
After, go to Tools > Options > Text Editor > C++ > Formatting and checking "Use custom clang-format.exe file" in Visual Studio and choose the clang-format.exe in the repository at /packages/clang-format.win-x86.10.0.0/tools/clang-format.exe by clicking "browse" right under the check box.
After, go to Tools > Options > Text Editor > C++ > Formatting and check "Use custom clang-format.exe file" in Visual Studio and choose the clang-format.exe in the repository at /packages/clang-format.win-x86.10.0.0/tools/clang-format.exe by clicking "browse" right under the check box.
### Building in PowerShell

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,171 @@
---
author: Carlos Zamora @carlos-zamora
created on: 2019-08-30
last updated: 2021-09-17
issue id: 715
---
# Keyboard Selection
## Abstract
This spec describes a new set of non-configurable keybindings that allows the user to update a selection without the use of a mouse or stylus.
## Inspiration
ConHost allows the user to modify a selection using the keyboard. Holding `Shift` allows the user to move the second selection endpoint in accordance with the arrow keys. The selection endpoint updates by one cell per key event, allowing the user to refine the selected region.
Mark mode allows the user to create a selection using only the keyboard, then edit it as mentioned above.
## Solution Design
The fundamental solution design for keyboard selection is that the responsibilities between the Terminal Control and Terminal Core must be very distinct. The Terminal Control is responsible for handling user interaction and directing the Terminal Core to update the selection. The Terminal Core will need to update the selection according to the preferences of the Terminal Control.
Relatively recently, TerminalControl was split into `TerminalControl`, `ControlInteractivity`, and `ControlCore`. Changes made to `ControlInteractivity`, `ControlCore`, and below propagate functionality to all consumers, meaning that the WPF terminal would benefit from these changes with no additional work required.
### Fundamental Terminal Control Changes
`ControlCore::TrySendKeyEvent()` is responsible for handling the key events after key bindings are dealt with in `TermControl`. At the time of writing this spec, there are 2 cases handled in this order:
- Clear the selection (except in a few key scenarios)
- Send Key Event
The first branch will be updated to _modify_ the selection instead of usually _clearing_ it. This will happen by converting the key event into parameters to forward to `TerminalCore`, which then updates the selection appropriately.
#### Idea: Make keyboard selection a collection of standard keybindings
One idea is to introduce an `updateSelection` action that conditionally works if a selection is active (similar to the `copy` action). For these key bindings, if there is no selection, the key events are forwarded to the application.
Thanks to Keybinding Args, there would only be 1 new command:
| Action | Keybinding Args | Description |
|--|--|--|
| `updateSelection` | | If a selection exists, moves the last selection endpoint. |
| | `Enum direction { up, down, left, right }` | The direction the selection will be moved in. |
| | `Enum mode { char, word, view, buffer }` | The context for which to move the selection endpoint to. (defaults to `char`) |
By default, the following keybindings will be set:
```JS
// Character Selection
{ "command": {"action": "updateSelection", "direction": "left", "mode": "char" }, "keys": "shift+left" },
{ "command": {"action": "updateSelection", "direction": "right", "mode": "char" }, "keys": "shift+right" },
{ "command": {"action": "updateSelection", "direction": "up", "mode": "char" }, "keys": "shift+up" },
{ "command": {"action": "updateSelection", "direction": "down", "mode": "char" }, "keys": "shift+down" },
// Word Selection
{ "command": {"action": "updateSelection", "direction": "left", "mode": "word" }, "keys": "ctrl+shift+left" },
{ "command": {"action": "updateSelection", "direction": "right", "mode": "word" }, "keys": "ctrl+shift+right" },
// Viewport Selection
{ "command": {"action": "updateSelection", "direction": "left", "mode": "view" }, "keys": "shift+home" },
{ "command": {"action": "updateSelection", "direction": "right", "mode": "view" }, "keys": "shift+end" },
{ "command": {"action": "updateSelection", "direction": "up", "mode": "view" }, "keys": "shift+pgup" },
{ "command": {"action": "updateSelection", "direction": "down", "mode": "view" }, "keys": "shift+pgdn" },
// Buffer Corner Selection
{ "command": {"action": "updateSelection", "direction": "up", "mode": "buffer" }, "keys": "ctrl+shift+home" },
{ "command": {"action": "updateSelection", "direction": "down", "mode": "buffer" }, "keys": "ctrl+shift+end" },
```
These are in accordance with ConHost's keyboard selection model.
This idea was abandoned due to several reasons:
1. Keyboard selection should be a standard way to interact with a terminal across all consumers (i.e. WPF control, etc.)
2. There isn't really another set of key bindings that makes sense for this. We already hardcoded <kbd>ESC</kbd> as a way to clear the selection. This is just an extension of that.
3. Adding 12 conditionally effective key bindings takes the spot of 12 potential non-conditional key bindings. It would be nice if a different key binding could be set when the selection is not active, but that makes the settings design much more complicated.
4. 12 new items in the command palette is also pretty excessive.
5. If proven wrong when this is in WT Preview, we can revisit this and make them customizable then. It's better to add the ability to customize it later than take it away.
#### Idea: Make keyboard selection a simulation of mouse selection
It may seem that some effort can be saved by making the keyboard selection act as a simulation of mouse selection. There is a union of mouse and keyboard activity that can be represented in a single set of selection motion interfaces that are commanded by the TermControl's Mouse/Keyboard handler and adapted into appropriate motions in the Terminal Core.
However, the mouse handler operates by translating a pixel coordinate on the screen to a text buffer coordinate. This would have to be rewritten and the approach was deemed unworthy.
### Fundamental Terminal Core Changes
The Terminal Core will need to expose a `UpdateSelection()` function that is called by the keybinding handler. The following parameters will need to be passed in:
- `enum SelectionDirection`: the direction that the selection endpoint will attempt to move to. Possible values include `Up`, `Down`, `Left`, and `Right`.
- `enum SelectionExpansion`: the selection expansion mode that the selection endpoint will adhere to. Possible values include `Char`, `Word`, `View`, `Buffer`.
#### Moving by Cell
For `SelectionExpansion = Char`, the selection endpoint will be updated according to the buffer's output pattern. For **horizontal movements**, the selection endpoint will attempt to move left or right. If a viewport boundary is hit, the endpoint will wrap appropriately (i.e.: hitting the left boundary moves it to the last cell of the line above it).
For **vertical movements**, the selection endpoint will attempt to move up or down. If a **viewport boundary** is hit and there is a scroll buffer, the endpoint will move and scroll accordingly by a line.
If a **buffer boundary** is hit, the endpoint will not move. In this case, however, the event will still be considered handled.
**NOTE**: An important thing to handle properly in all cases is wide glyphs. The user should not be allowed to select a portion of a wide glyph; it should be all or none of it. When calling `_ExpandWideGlyphSelection` functions, the result must be saved to the endpoint.
#### Moving by Word
For `SelectionExpansion = Word`, the selection endpoint will also be updated according to the buffer's output pattern, as above. However, the selection will be updated in accordance with "chunk selection" (performing a double-click and dragging the mouse to expand the selection). For **horizontal movements**, the selection endpoint will be updated according to the `_ExpandDoubleClickSelection` functions. The result must be saved to the endpoint. As before, if a boundary is hit, the endpoint will wrap appropriately. See [Future Considerations](#FutureConsiderations) for how this will interact with line wrapping.
For **vertical movements**, the movement is a little more complicated than before. The selection will still respond to buffer and viewport boundaries as before. If the user is trying to move up, the selection endpoint will attempt to move up by one line, then selection will be expanded leftwards. Alternatively, if the user is trying to move down, the selection endpoint will attempt to move down by one line, then the selection will be expanded rightwards.
#### Moving by Viewport
For `SelectionExpansion = View`, the selection endpoint will be updated according to the viewport's height. Horizontal movements will be updated according to the viewport's width, thus resulting in the endpoint being moved to the left/right boundary of the viewport.
#### Moving by Buffer
For `SelectionExpansion = Buffer`, the selection endpoint will be moved to the beginning or end of all the text within the buffer. If moving up or left, set the position to 0,0 (the origin of the buffer). If moving down or right, set the position to the last character in the buffer.
**NOTE**: In all cases, horizontal movements attempting to move past the left/right viewport boundaries result in a wrap. Vertical movements attempting to move past the top/bottom viewport boundaries will scroll such that the selection is at the edge of the screen. Vertical movements attempting to move past the top/bottom buffer boundaries will be clamped to be within buffer boundaries.
Every combination of the `SelectionDirection` and `SelectionExpansion` will map to a keybinding. These pairings are shown below in the UI/UX Design --> Keybindings section.
**NOTE**: If `copyOnSelect` is enabled, we need to make sure we **DO NOT** update the clipboard on every change in selection. The user must explicitly choose to copy the selected text from the buffer.
## UI/UX Design
### Key Bindings
There will only be 1 new command that needs to be added:
| Action | Keybinding Args | Description |
|--|--|--|
| `selectAll` | | Select the entire text buffer.
By default, the following key binding will be set:
```JS
{ "command": "selectAll", "keys": "ctrl+shift+a" },
```
## Capabilities
### Accessibility
Using the keyboard is generally a more accessible experience than using the mouse. Being able to modify a selection by using the keyboard is a good first step towards making selecting text more accessible.
### Security
N/A
### Reliability
With regards to the Terminal Core, the newly introduced code should rely on already existing and tested code. Thus no crash-related bugs are expected.
With regards to Terminal Control and the settings model, crash-related bugs are not expected. However, ensuring that the selection is updated and cleared in general use-case scenarios must be ensured.
### Compatibility
N/A
### Performance, Power, and Efficiency
## Potential Issues
### Grapheme Clusters
When grapheme cluster support is inevitably added to the Text Buffer, moving by "cell" is expected to move by "character" or "cluster". This is similar to how wide glyphs are handled today. Either all of it is selected, or none of it.
## Future considerations
### Word Selection Wrap
At the time of writing this spec, expanding or moving by word is interrupted by the beginning or end of the line, regardless of the wrap flag being set. In the future, selection and the accessibility models will respect the wrap flag on the text buffer.
## Mark Mode
This functionality will be expanded to create a feature similar to Mark Mode. This will allow a user to create a selection using only the keyboard.
## Resources
- https://blogs.windows.com/windowsdeveloper/2014/10/07/console-improvements-in-the-windows-10-technical-preview/

View File

@ -12,6 +12,7 @@
<EventProvider Id="EventProvider_TerminalWin32Host" Name="56c06166-2e2e-5f4d-7ff3-74f4b78c87d6" />
<EventProvider Id="EventProvider_TerminalRemoting" Name="d6f04aad-629f-539a-77c1-73f5c3e4aa7b" />
<EventProvider Id="EventProvider_TerminalDirectX" Name="c93e739e-ae50-5a14-78e7-f171e947535d" />
<EventProvider Id="EventProvider_TerminalUIA" Name="e7ebce59-2161-572d-b263-2f16a6afb9e5"/>
<Profile Id="Terminal.Verbose.File" Name="Terminal" Description="Terminal" LoggingMode="File" DetailLevel="Verbose">
<Collectors>
<EventCollectorId Value="EventCollector_Terminal">
@ -23,6 +24,7 @@
<EventProviderId Value="EventProvider_TerminalWin32Host" />
<EventProviderId Value="EventProvider_TerminalRemoting" />
<EventProviderId Value="EventProvider_TerminalDirectX" />
<EventProviderId Value="EventProvider_TerminalUIA" />
</EventProviders>
</EventCollectorId>
</Collectors>

View File

@ -1430,12 +1430,13 @@ const til::point TextBuffer::GetGlyphStart(const til::point pos, std::optional<t
}
// Method Description:
// - Update pos to be the end of the current glyph/character. This is used for accessibility
// - Update pos to be the end of the current glyph/character.
// Arguments:
// - pos - a COORD on the word you are currently on
// - accessibilityMode - this is being used for accessibility; make the end exclusive.
// Return Value:
// - pos - The COORD for the last cell of the current glyph (exclusive)
const til::point TextBuffer::GetGlyphEnd(const til::point pos, std::optional<til::point> limitOptional) const
const til::point TextBuffer::GetGlyphEnd(const til::point pos, bool accessibilityMode, std::optional<til::point> limitOptional) const
{
COORD resultPos = pos;
const auto bufferSize = GetSize();
@ -1453,7 +1454,10 @@ const til::point TextBuffer::GetGlyphEnd(const til::point pos, std::optional<til
}
// increment one more time to become exclusive
bufferSize.IncrementInBounds(resultPos, true);
if (accessibilityMode)
{
bufferSize.IncrementInBounds(resultPos, true);
}
return resultPos;
}

View File

@ -147,7 +147,7 @@ public:
bool MoveToPreviousWord(COORD& pos, const std::wstring_view wordDelimiters) const;
const til::point GetGlyphStart(const til::point pos, std::optional<til::point> limitOptional = std::nullopt) const;
const til::point GetGlyphEnd(const til::point pos, std::optional<til::point> limitOptional = std::nullopt) const;
const til::point GetGlyphEnd(const til::point pos, bool accessibilityMode = false, std::optional<til::point> limitOptional = std::nullopt) const;
bool MoveToNextGlyph(til::point& pos, bool allowBottomExclusive = false, std::optional<til::point> limitOptional = std::nullopt) const;
bool MoveToPreviousGlyph(til::point& pos, std::optional<til::point> limitOptional = std::nullopt) const;

View File

@ -465,6 +465,10 @@ namespace SettingsModelLocalTests
"name":"action7_startingDirectoryWithTrailingSlash",
"command": { "action": "newWindow", "startingDirectory":"C:\\", "commandline": "bar.exe" }
},
{
"name":"action8_tabTitleEscaping",
"command": { "action": "newWindow", "tabTitle":"\\\";foo\\" }
}
])" };
const auto commands0Json = VerifyParseSucceeded(commands0String);
@ -473,7 +477,7 @@ namespace SettingsModelLocalTests
VERIFY_ARE_EQUAL(0u, commands.Size());
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
VERIFY_ARE_EQUAL(0u, warnings.size());
VERIFY_ARE_EQUAL(8u, commands.Size());
VERIFY_ARE_EQUAL(9u, commands.Size());
{
auto command = commands.Lookup(L"action0");
@ -586,5 +590,20 @@ namespace SettingsModelLocalTests
L"cmdline: \"%s\"", cmdline.c_str()));
VERIFY_ARE_EQUAL(L"--startingDirectory \"C:\\\\\" -- \"bar.exe\"", terminalArgs.ToCommandline());
}
{
auto command = commands.Lookup(L"action8_tabTitleEscaping");
VERIFY_IS_NOT_NULL(command);
VERIFY_IS_NOT_NULL(command.ActionAndArgs());
VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action());
const auto& realArgs = command.ActionAndArgs().Args().try_as<NewWindowArgs>();
VERIFY_IS_NOT_NULL(realArgs);
const auto& terminalArgs = realArgs.TerminalArgs();
VERIFY_IS_NOT_NULL(terminalArgs);
auto cmdline = terminalArgs.ToCommandline();
Log::Comment(NoThrowString().Format(
L"cmdline: \"%s\"", cmdline.c_str()));
VERIFY_ARE_EQUAL(LR"-(--title "\\\"\;foo\\")-", terminalArgs.ToCommandline());
}
}
}

View File

@ -751,7 +751,7 @@ namespace TerminalAppLocalTests
});
VERIFY_SUCCEEDED(result);
Log::Comment(L"Move focus. This will cause us to un-zoom.");
Log::Comment(L"Move focus. We should still be zoomed.");
result = RunOnUIThread([&page]() {
// Set up action
MoveFocusArgs args{ FocusDirection::Left };
@ -761,7 +761,7 @@ namespace TerminalAppLocalTests
auto firstTab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(2, firstTab->GetLeafPaneCount());
VERIFY_IS_FALSE(firstTab->IsZoomed());
VERIFY_IS_TRUE(firstTab->IsZoomed());
});
VERIFY_SUCCEEDED(result);
}
@ -1357,7 +1357,8 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be changed to the preview");
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
// And we should have stored a function to revert the change.
VERIFY_ARE_EQUAL(1u, page->_restorePreviewFuncs.size());
});
TestOnUIThread([&page]() {
@ -1383,7 +1384,8 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be changed");
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
// After preview there should be no more restore functions to execute.
VERIFY_ARE_EQUAL(0u, page->_restorePreviewFuncs.size());
});
}
@ -1428,7 +1430,6 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be changed to the preview");
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
});
TestOnUIThread([&page]() {
@ -1451,7 +1452,6 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be the same as it originally was");
VERIFY_ARE_EQUAL(til::color{ 0xff0c0c0c }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
});
}
@ -1498,7 +1498,6 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be changed to the preview");
VERIFY_ARE_EQUAL(til::color{ 0xff000000 }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
});
TestOnUIThread([&page]() {
@ -1522,7 +1521,6 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be changed to the preview");
VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(originalSettings, page->_originalSettings);
});
TestOnUIThread([&page]() {
@ -1548,7 +1546,6 @@ namespace TerminalAppLocalTests
Log::Comment(L"Color should be changed");
VERIFY_ARE_EQUAL(til::color{ 0xffFAFAFA }, controlSettings.DefaultBackground());
VERIFY_ARE_EQUAL(nullptr, page->_originalSettings);
});
}

View File

@ -549,11 +549,11 @@ try
if (multiClickMapper == 3)
{
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansionMode::Line);
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansion::Line);
}
else if (multiClickMapper == 2)
{
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansionMode::Word);
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansion::Word);
}
else
{

View File

@ -0,0 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "GetWindowLayoutArgs.h"
#include "GetWindowLayoutArgs.g.cpp"

View File

@ -0,0 +1,32 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Class Name:
- GetWindowLayoutArgs.h
Abstract:
- This is a helper class for getting the window layout from a peasant.
Depending on if we are running on the monarch or on a peasant we might need
to switch what thread we are executing on. This gives us the option of
either returning the json result synchronously, or as a promise.
--*/
#pragma once
#include "GetWindowLayoutArgs.g.h"
#include "../cascadia/inc/cppwinrt_utils.h"
namespace winrt::Microsoft::Terminal::Remoting::implementation
{
struct GetWindowLayoutArgs : public GetWindowLayoutArgsT<GetWindowLayoutArgs>
{
WINRT_PROPERTY(winrt::hstring, WindowLayoutJson, L"");
WINRT_PROPERTY(winrt::Windows::Foundation::IAsyncOperation<winrt::hstring>, WindowLayoutJsonAsync, nullptr)
};
}
namespace winrt::Microsoft::Terminal::Remoting::factory_implementation
{
BASIC_FACTORY(GetWindowLayoutArgs);
}

View File

@ -12,7 +12,6 @@
</PropertyGroup>
<Import Project="..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="Monarch.h">
@ -36,6 +35,12 @@
<ClInclude Include="WindowActivatedArgs.h">
<DependentUpon>Peasant.idl</DependentUpon>
</ClInclude>
<ClInclude Include="GetWindowLayoutArgs.h">
<DependentUpon>Peasant.idl</DependentUpon>
</ClInclude>
<ClInclude Include="QuitAllRequestedArgs.h">
<DependentUpon>Monarch.idl</DependentUpon>
</ClInclude>
<ClInclude Include="pch.h" />
<ClInclude Include="MonarchFactory.h" />
<ClInclude Include="Peasant.h">
@ -71,6 +76,12 @@
<ClCompile Include="WindowActivatedArgs.cpp">
<DependentUpon>Peasant.idl</DependentUpon>
</ClCompile>
<ClCompile Include="GetWindowLayoutArgs.cpp">
<DependentUpon>Peasant.idl</DependentUpon>
</ClCompile>
<ClCompile Include="QuitAllRequestedArgs.cpp">
<DependentUpon>Monarch.idl</DependentUpon>
</ClCompile>
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
@ -128,6 +139,5 @@
</ItemDefinitionGroup>
<!-- ========================= Globals ======================== -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
</Project>
</Project>

View File

@ -6,6 +6,7 @@
#include "Monarch.h"
#include "CommandlineArgs.h"
#include "FindTargetWindowArgs.h"
#include "QuitAllRequestedArgs.h"
#include "ProposeCommandlineResult.h"
#include "Monarch.g.cpp"
@ -135,12 +136,18 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
// - <none> used
// Return Value:
// - <none>
void Monarch::_handleQuitAll(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*args*/)
winrt::fire_and_forget Monarch::_handleQuitAll(const winrt::Windows::Foundation::IInspectable& /*sender*/,
const winrt::Windows::Foundation::IInspectable& /*args*/)
{
// Let the process hosting the monarch run any needed logic before
// closing all windows.
_QuitAllRequestedHandlers(*this, nullptr);
auto args = winrt::make_self<implementation::QuitAllRequestedArgs>();
_QuitAllRequestedHandlers(*this, *args);
if (const auto action = args->BeforeQuitAllAction())
{
co_await action;
}
_quitting.store(true);
// Tell all peasants to exit.
@ -994,4 +1001,28 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
_forEachPeasant(func, onError);
}
// Method Description:
// - Ask all peasants to return their window layout as json
// Arguments:
// - <none>
// Return Value:
// - The collection of window layouts from each peasant.
Windows::Foundation::Collections::IVector<winrt::hstring> Monarch::GetAllWindowLayouts()
{
std::vector<winrt::hstring> vec;
auto callback = [&](const auto& /*id*/, const auto& p) {
vec.emplace_back(p.GetWindowLayout());
};
auto onError = [](auto&& id) {
TraceLoggingWrite(g_hRemotingProvider,
"Monarch_GetAllWindowLayouts_Failed",
TraceLoggingInt64(id, "peasantID", "The ID of the peasant which we could not get a window layout from"),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
};
_forEachPeasant(callback, onError);
return winrt::single_threaded_vector(std::move(vec));
}
}

View File

@ -59,13 +59,14 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void SummonAllWindows();
bool DoesQuakeWindowExist();
Windows::Foundation::Collections::IVectorView<winrt::Microsoft::Terminal::Remoting::PeasantInfo> GetPeasantInfos();
Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts();
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(ShowNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(HideNotificationIconRequested, 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(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::QuitAllRequestedArgs);
private:
uint64_t _ourPID;
@ -103,8 +104,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
void _renameRequested(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Microsoft::Terminal::Remoting::RenameRequestArgs& args);
void _handleQuitAll(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
winrt::fire_and_forget _handleQuitAll(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::Foundation::IInspectable& args);
// Method Description:
// - Helper for doing something on each and every peasant.
@ -177,6 +178,10 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
}
_clearOldMruEntries(peasantsToErase);
// A peasant died, let the app host know that the number of
// windows has changed.
_WindowClosedHandlers(nullptr, nullptr);
}
}

View File

@ -31,6 +31,12 @@ namespace Microsoft.Terminal.Remoting
Windows.Foundation.IReference<UInt64> WindowID;
}
[default_interface] runtimeclass QuitAllRequestedArgs
{
QuitAllRequestedArgs();
Windows.Foundation.IAsyncAction BeforeQuitAllAction;
}
struct PeasantInfo
{
UInt64 Id;
@ -52,12 +58,13 @@ namespace Microsoft.Terminal.Remoting
void SummonAllWindows();
Boolean DoesQuakeWindowExist();
Windows.Foundation.Collections.IVectorView<PeasantInfo> GetPeasantInfos { get; };
Windows.Foundation.Collections.IVector<String> GetAllWindowLayouts();
event Windows.Foundation.TypedEventHandler<Object, FindTargetWindowArgs> FindTargetWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowCreated;
event Windows.Foundation.TypedEventHandler<Object, Object> WindowClosed;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitAllRequested;
event Windows.Foundation.TypedEventHandler<Object, QuitAllRequestedArgs> QuitAllRequested;
};
}

View File

@ -5,6 +5,7 @@
#include "Peasant.h"
#include "CommandlineArgs.h"
#include "SummonWindowBehavior.h"
#include "GetWindowLayoutArgs.h"
#include "Peasant.g.cpp"
#include "../../types/inc/utils.hpp"
@ -289,4 +290,24 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE),
TraceLoggingKeyword(TIL_KEYWORD_TRACE));
}
// Method Description:
// - Request and return the window layout from the current TerminalPage
// Arguments:
// - <none>
// Return Value:
// - the window layout as a json string
hstring Peasant::GetWindowLayout()
{
auto args = winrt::make_self<implementation::GetWindowLayoutArgs>();
_GetWindowLayoutRequestedHandlers(nullptr, *args);
if (const auto op = args->WindowLayoutJsonAsync())
{
// This will fail if called on the UI thread, so the monarch should
// never set WindowLayoutJsonAsync.
auto str = op.get();
return str;
}
return args->WindowLayoutJson();
}
}

View File

@ -36,6 +36,9 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
winrt::Microsoft::Terminal::Remoting::WindowActivatedArgs GetLastActivatedArgs();
winrt::Microsoft::Terminal::Remoting::CommandlineArgs InitialArgs();
winrt::hstring GetWindowLayout();
WINRT_PROPERTY(winrt::hstring, WindowName);
WINRT_PROPERTY(winrt::hstring, ActiveTabTitle);
@ -49,6 +52,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
TYPED_EVENT(HideNotificationIconRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitAllRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(QuitRequested, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
TYPED_EVENT(GetWindowLayoutRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::GetWindowLayoutArgs);
private:
Peasant(const uint64_t testPID);

View File

@ -30,6 +30,11 @@ namespace Microsoft.Terminal.Remoting
Windows.Foundation.DateTime ActivatedTime { get; };
};
[default_interface] runtimeclass GetWindowLayoutArgs {
GetWindowLayoutArgs();
String WindowLayoutJson;
Windows.Foundation.IAsyncOperation<String> WindowLayoutJsonAsync;
}
enum MonitorBehavior
{
@ -69,6 +74,7 @@ namespace Microsoft.Terminal.Remoting
void RequestHideNotificationIcon();
void RequestQuitAll();
void Quit();
String GetWindowLayout();
event Windows.Foundation.TypedEventHandler<Object, WindowActivatedArgs> WindowActivated;
event Windows.Foundation.TypedEventHandler<Object, CommandlineArgs> ExecuteCommandlineRequested;
@ -78,6 +84,7 @@ namespace Microsoft.Terminal.Remoting
event Windows.Foundation.TypedEventHandler<Object, SummonWindowBehavior> SummonRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> ShowNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> HideNotificationIconRequested;
event Windows.Foundation.TypedEventHandler<Object, GetWindowLayoutArgs> GetWindowLayoutRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitAllRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
};

View File

@ -0,0 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "QuitAllRequestedArgs.h"
#include "QuitAllRequestedArgs.g.cpp"

View File

@ -0,0 +1,30 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Class Name:
- QuitAllRequestedArgs.h
Abstract:
- This is a helper class for allowing the monarch to run code before telling all
peasants to quit. This way the monarch can raise an event and get back a future
to wait for before continuing.
--*/
#pragma once
#include "QuitAllRequestedArgs.g.h"
#include "../cascadia/inc/cppwinrt_utils.h"
namespace winrt::Microsoft::Terminal::Remoting::implementation
{
struct QuitAllRequestedArgs : public QuitAllRequestedArgsT<QuitAllRequestedArgs>
{
WINRT_PROPERTY(winrt::Windows::Foundation::IAsyncAction, BeforeQuitAllAction, nullptr)
};
}
namespace winrt::Microsoft::Terminal::Remoting::factory_implementation
{
BASIC_FACTORY(QuitAllRequestedArgs);
}

View File

@ -271,7 +271,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
_monarch.FindTargetWindowRequested({ this, &WindowManager::_raiseFindTargetWindowRequested });
_monarch.ShowNotificationIconRequested([this](auto&&, auto&&) { _ShowNotificationIconRequestedHandlers(*this, nullptr); });
_monarch.HideNotificationIconRequested([this](auto&&, auto&&) { _HideNotificationIconRequestedHandlers(*this, nullptr); });
_monarch.QuitAllRequested([this](auto&&, auto&&) { _QuitAllRequestedHandlers(*this, nullptr); });
_monarch.QuitAllRequested({ get_weak(), &WindowManager::_QuitAllRequestedHandlers });
_BecameMonarchHandlers(*this, nullptr);
}
@ -318,6 +318,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
}
}
_peasant.GetWindowLayoutRequested({ get_weak(), &WindowManager::_GetWindowLayoutRequestedHandlers });
TraceLoggingWrite(g_hRemotingProvider,
"WindowManager_CreateOurPeasant",
TraceLoggingUInt64(_peasant.GetID(), "peasantID", "The ID of our new peasant"),
@ -610,4 +612,17 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
{
winrt::get_self<implementation::Peasant>(_peasant)->ActiveTabTitle(title);
}
Windows::Foundation::Collections::IVector<winrt::hstring> WindowManager::GetAllWindowLayouts()
{
if (_monarch)
{
try
{
return _monarch.GetAllWindowLayouts();
}
CATCH_LOG()
}
return nullptr;
}
}

View File

@ -50,6 +50,7 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
winrt::fire_and_forget RequestQuitAll();
bool DoesQuakeWindowExist();
void UpdateActiveTabTitle(winrt::hstring title);
Windows::Foundation::Collections::IVector<winrt::hstring> GetAllWindowLayouts();
TYPED_EVENT(FindTargetWindowRequested, winrt::Windows::Foundation::IInspectable, winrt::Microsoft::Terminal::Remoting::FindTargetWindowArgs);
TYPED_EVENT(BecameMonarch, winrt::Windows::Foundation::IInspectable, winrt::Windows::Foundation::IInspectable);
@ -57,7 +58,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
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::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);
private:
bool _shouldCreateWindow{ false };

View File

@ -16,6 +16,8 @@ namespace Microsoft.Terminal.Remoting
void SummonAllWindows();
void RequestShowNotificationIcon();
void RequestHideNotificationIcon();
Windows.Foundation.Collections.IVector<String> GetAllWindowLayouts();
UInt64 GetNumberOfPeasants();
void RequestQuitAll();
void UpdateActiveTabTitle(String title);
@ -25,8 +27,9 @@ namespace Microsoft.Terminal.Remoting
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, Object> QuitAllRequested;
};
}

View File

@ -55,9 +55,7 @@ HRESULT OpenTerminalHere::Invoke(IShellItemArray* psiItemArray,
STARTUPINFOEX siEx{ 0 };
siEx.StartupInfo.cb = sizeof(STARTUPINFOEX);
// Append a "\." to the given path, so that this will work in "C:\"
auto path{ wil::str_printf<std::wstring>(LR"-(%s\.)-", pszName.get()) };
auto cmdline{ wil::str_printf<std::wstring>(LR"-("%s" -d "%s")-", GetWtExePath().c_str(), path.c_str()) };
auto cmdline{ wil::str_printf<std::wstring>(LR"-("%s" -d %s)-", GetWtExePath().c_str(), QuoteAndEscapeCommandlineArg(pszName.get()).c_str()) };
RETURN_IF_WIN32_BOOL_FALSE(CreateProcessW(
nullptr, // lpApplicationName
cmdline.data(),
@ -66,7 +64,7 @@ HRESULT OpenTerminalHere::Invoke(IShellItemArray* psiItemArray,
false, // bInheritHandles
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
nullptr, // lpEnvironment
path.data(),
pszName.get(),
&siEx.StartupInfo, // lpStartupInfo
&_piClient // lpProcessInformation
));

View File

@ -67,41 +67,17 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalPage::_EndPreviewColorScheme()
{
// Get the focused control
if (const auto& activeControl{ _GetActiveControl() })
for (const auto& f : _restorePreviewFuncs)
{
// Get the runtime settings of the focused control
const auto& controlSettings{ activeControl.Settings().as<TerminalSettings>() };
// Get the control's root settings, the ones that we actually
// assigned to it.
auto parentSettings{ controlSettings.GetParent() };
while (parentSettings.GetParent() != nullptr)
{
parentSettings = parentSettings.GetParent();
}
// If the root settings are the same as the ones we stashed,
// then reset the parent of the runtime settings to the stashed
// settings. This condition might be false if the settings
// hot-reloaded while the palette was open. In that case, we
// don't want to reset the settings to what they were _before_
// the hot-reload.
if (_originalSettings == parentSettings)
{
// Set the original settings as the parent of the control's settings
activeControl.Settings().as<TerminalSettings>().SetParent(_originalSettings);
}
activeControl.UpdateSettings();
f();
}
_originalSettings = nullptr;
_restorePreviewFuncs.clear();
}
// Method Description:
// - Preview handler for the SetColorScheme action.
// - This method will stash the settings of the current control in
// _originalSettings. Then it will create a new TerminalSettings object
// - This method will stash functions to reset the settings of the selected controls in
// _restorePreviewFuncs. Then it will create a new TerminalSettings object
// with only the properties from the ColorScheme set. It'll _insert_ a
// TerminalSettings between the control's root settings (built from
// CascadiaSettings) and the control's runtime settings. That'll cause the
@ -112,33 +88,63 @@ namespace winrt::TerminalApp::implementation
// - <none>
void TerminalPage::_PreviewColorScheme(const Settings::Model::SetColorSchemeArgs& args)
{
// Get the focused control
if (const auto& activeControl{ _GetActiveControl() })
if (const auto& scheme{ _settings.GlobalSettings().ColorSchemes().TryLookup(args.SchemeName()) })
{
if (const auto& scheme{ _settings.GlobalSettings().ColorSchemes().TryLookup(args.SchemeName()) })
{
// Clear the saved preview funcs because we don't need to add a restore each time
// the preview color changes, we only need to be able to restore the last one.
_restorePreviewFuncs.clear();
_ApplyToActiveControls([&](const auto& control) {
// Get the settings of the focused control and stash them
const auto& controlSettings = activeControl.Settings().as<TerminalSettings>();
const auto& controlSettings = control.Settings().as<TerminalSettings>();
// Make sure to recurse up to the root - if you're doing
// this while you're currently previewing a SetColorScheme
// action, then the parent of the control's settings is _the
// last preview TerminalSettings we inserted! We don't want
// to save that one!
_originalSettings = controlSettings.GetParent();
while (_originalSettings.GetParent() != nullptr)
auto originalSettings = controlSettings.GetParent();
while (originalSettings.GetParent() != nullptr)
{
_originalSettings = _originalSettings.GetParent();
originalSettings = originalSettings.GetParent();
}
// Create a new child for those settings
TerminalSettingsCreateResult fake{ _originalSettings };
TerminalSettingsCreateResult fake{ originalSettings };
const auto& childStruct = TerminalSettings::CreateWithParent(fake);
// Modify the child to have the applied color scheme
childStruct.DefaultSettings().ApplyColorScheme(scheme);
// Insert that new child as the parent of the control's settings
controlSettings.SetParent(childStruct.DefaultSettings());
activeControl.UpdateSettings();
}
control.UpdateSettings();
// Take a copy of the inputs, since they are pointers anyways.
_restorePreviewFuncs.emplace_back([=]() {
// Get the runtime settings of the focused control
const auto& controlSettings{ control.Settings().as<TerminalSettings>() };
// Get the control's root settings, the ones that we actually
// assigned to it.
auto parentSettings{ controlSettings.GetParent() };
while (parentSettings.GetParent() != nullptr)
{
parentSettings = parentSettings.GetParent();
}
// If the root settings are the same as the ones we stashed,
// then reset the parent of the runtime settings to the stashed
// settings. This condition might be false if the settings
// hot-reloaded while the palette was open. In that case, we
// don't want to reset the settings to what they were _before_
// the hot-reload.
if (originalSettings == parentSettings)
{
// Set the original settings as the parent of the control's settings
control.Settings().as<TerminalSettings>().SetParent(originalSettings);
}
control.UpdateSettings();
});
});
}
}

View File

@ -78,7 +78,7 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_HandleCloseWindow(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
CloseWindow(false);
_CloseRequestedHandlers(nullptr, nullptr);
args.Handled(true);
}
@ -377,11 +377,10 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = args.ActionArgs().try_as<AdjustFontSizeArgs>())
{
if (const auto& termControl{ _GetActiveControl() })
{
termControl.AdjustFontSize(realArgs.Delta());
args.Handled(true);
}
const auto res = _ApplyToActiveControls([&](auto& control) {
control.AdjustFontSize(realArgs.Delta());
});
args.Handled(res);
}
}
@ -395,21 +394,19 @@ namespace winrt::TerminalApp::implementation
void TerminalPage::_HandleResetFontSize(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (const auto& termControl{ _GetActiveControl() })
{
termControl.ResetFontSize();
args.Handled(true);
}
const auto res = _ApplyToActiveControls([](auto& control) {
control.ResetFontSize();
});
args.Handled(res);
}
void TerminalPage::_HandleToggleShaderEffects(const IInspectable& /*sender*/,
const ActionEventArgs& args)
{
if (const auto& termControl{ _GetActiveControl() })
{
termControl.ToggleShaderEffects();
args.Handled(true);
}
const auto res = _ApplyToActiveControls([](auto& control) {
control.ToggleShaderEffects();
});
args.Handled(res);
}
void TerminalPage::_HandleToggleFocusMode(const IInspectable& /*sender*/,
@ -452,37 +449,33 @@ namespace winrt::TerminalApp::implementation
args.Handled(false);
if (const auto& realArgs = args.ActionArgs().try_as<SetColorSchemeArgs>())
{
if (const auto activeTab{ _GetFocusedTabImpl() })
if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName()))
{
if (auto activeControl = activeTab->GetActiveTerminalControl())
{
if (const auto scheme = _settings.GlobalSettings().ColorSchemes().TryLookup(realArgs.SchemeName()))
const auto res = _ApplyToActiveControls([&](auto& control) {
// Start by getting the current settings of the control
auto controlSettings = control.Settings().as<TerminalSettings>();
auto parentSettings = controlSettings;
// Those are the _runtime_ settings however. What we
// need to do is:
//
// 1. Blow away any colors set in the runtime settings.
// 2. Apply the color scheme to the parent settings.
//
// 1 is important to make sure that the effects of
// something like `colortool` are cleared when setting
// the scheme.
if (controlSettings.GetParent() != nullptr)
{
// Start by getting the current settings of the control
auto controlSettings = activeControl.Settings().as<TerminalSettings>();
auto parentSettings = controlSettings;
// Those are the _runtime_ settings however. What we
// need to do is:
//
// 1. Blow away any colors set in the runtime settings.
// 2. Apply the color scheme to the parent settings.
//
// 1 is important to make sure that the effects of
// something like `colortool` are cleared when setting
// the scheme.
if (controlSettings.GetParent() != nullptr)
{
parentSettings = controlSettings.GetParent();
}
// ApplyColorScheme(nullptr) will clear the old color scheme.
controlSettings.ApplyColorScheme(nullptr);
parentSettings.ApplyColorScheme(scheme);
activeControl.UpdateSettings();
args.Handled(true);
parentSettings = controlSettings.GetParent();
}
}
// ApplyColorScheme(nullptr) will clear the old color scheme.
controlSettings.ApplyColorScheme(nullptr);
parentSettings.ApplyColorScheme(scheme);
control.UpdateSettings();
});
args.Handled(res);
}
}
}
@ -896,11 +889,10 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = args.ActionArgs().try_as<ClearBufferArgs>())
{
if (const auto termControl{ _GetActiveControl() })
{
termControl.ClearBuffer(realArgs.Clear());
args.Handled(true);
}
const auto res = _ApplyToActiveControls([&](auto& control) {
control.ClearBuffer(realArgs.Clear());
});
args.Handled(res);
}
}
}

View File

@ -187,6 +187,10 @@ void AppCommandlineArgs::_buildParser()
_windowTarget,
RS_A(L"CmdWindowTargetArgDesc"));
_app.add_option("-s,--saved",
_loadPersistedLayoutIdx,
RS_A(L"CmdSavedLayoutArgDesc"));
// Subcommands
_buildNewTabParser();
_buildSplitPaneParser();
@ -700,6 +704,7 @@ void AppCommandlineArgs::_resetStateToDefault()
_swapPaneDirection = FocusDirection::None;
_focusPaneTarget = -1;
_loadPersistedLayoutIdx = -1;
// DON'T clear _launchMode here! This will get called once for every
// subcommand, so we don't want `wt -F new-tab ; split-pane` clearing out
@ -915,6 +920,12 @@ void AppCommandlineArgs::ValidateStartupCommands()
}
}
}
std::optional<uint32_t> AppCommandlineArgs::GetPersistedLayoutIdx() const noexcept
{
return _loadPersistedLayoutIdx >= 0 ?
std::optional{ static_cast<uint32_t>(_loadPersistedLayoutIdx) } :
std::nullopt;
}
std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchMode> AppCommandlineArgs::GetLaunchMode() const noexcept
{

View File

@ -39,6 +39,7 @@ public:
const std::string& GetExitMessage();
bool ShouldExitEarly() const noexcept;
std::optional<uint32_t> GetPersistedLayoutIdx() const noexcept;
std::optional<winrt::Microsoft::Terminal::Settings::Model::LaunchMode> GetLaunchMode() const noexcept;
int ParseArgs(const winrt::Microsoft::Terminal::Settings::Model::ExecuteCommandlineArgs& args);
@ -123,6 +124,7 @@ private:
std::string _exitMessage;
bool _shouldExitEarly{ false };
int _loadPersistedLayoutIdx{};
std::string _windowTarget{};
// Are you adding more args or attributes here? If they are not reset in _resetStateToDefault, make sure to reset them in FullResetState

View File

@ -600,13 +600,11 @@ namespace winrt::TerminalApp::implementation
winrt::Windows::Foundation::Size proposedSize{};
const float scale = static_cast<float>(dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI);
if (_root->ShouldUsePersistedLayout(_settings))
if (const auto layout = _root->LoadPersistedLayout(_settings))
{
const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts();
if (layouts && layouts.Size() > 0 && layouts.GetAt(0).InitialSize())
if (layout.InitialSize())
{
proposedSize = layouts.GetAt(0).InitialSize().Value();
proposedSize = layout.InitialSize().Value();
// The size is saved as a non-scaled real pixel size,
// so we need to scale it appropriately.
proposedSize.Height = proposedSize.Height * scale;
@ -704,13 +702,11 @@ namespace winrt::TerminalApp::implementation
auto initialPosition{ _settings.GlobalSettings().InitialPosition() };
if (_root->ShouldUsePersistedLayout(_settings))
if (const auto layout = _root->LoadPersistedLayout(_settings))
{
const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts();
if (layouts && layouts.Size() > 0 && layouts.GetAt(0).InitialPosition())
if (layout.InitialPosition())
{
initialPosition = layouts.GetAt(0).InitialPosition().Value();
initialPosition = layout.InitialPosition().Value();
}
}
@ -1151,10 +1147,22 @@ namespace winrt::TerminalApp::implementation
// - <none>
// Return Value:
// - <none>
void AppLogic::WindowCloseButtonClicked()
void AppLogic::CloseWindow(LaunchPosition pos)
{
if (_root)
{
// If persisted layout is enabled and we are the last window closing
// we should save our state.
if (_root->ShouldUsePersistedLayout(_settings) && _numOpenWindows == 1)
{
if (const auto layout = _root->GetWindowLayout())
{
layout.InitialPosition(pos);
const auto state = ApplicationState::SharedInstance();
state.PersistedWindowLayouts(winrt::single_threaded_vector<WindowLayout>({ layout }));
}
}
_root->CloseWindow(false);
}
}
@ -1168,6 +1176,16 @@ namespace winrt::TerminalApp::implementation
return {};
}
bool AppLogic::HasCommandlineArguments() const noexcept
{
return _hasCommandLineArguments;
}
bool AppLogic::HasSettingsStartupActions() const noexcept
{
return _hasSettingsStartupActions;
}
// Method Description:
// - Sets the initial commandline to process on startup, and attempts to
// parse it. Commands will be parsed into a list of ShortcutActions that
@ -1191,6 +1209,10 @@ namespace winrt::TerminalApp::implementation
// then it contains only the executable name and no other arguments.
_hasCommandLineArguments = args.size() > 1;
_appArgs.ValidateStartupCommands();
if (const auto idx = _appArgs.GetPersistedLayoutIdx())
{
_root->SetPersistedLayoutIdx(idx.value());
}
_root->SetStartupActions(_appArgs.GetStartupActions());
// Check if we were started as a COM server for inbound connections of console sessions
@ -1428,6 +1450,40 @@ namespace winrt::TerminalApp::implementation
return _settings.GlobalSettings().ActionMap().GlobalHotkeys();
}
bool AppLogic::ShouldUsePersistedLayout()
{
return _root != nullptr ? _root->ShouldUsePersistedLayout(_settings) : false;
}
void AppLogic::SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector<hstring>& layouts)
{
std::vector<WindowLayout> converted;
converted.reserve(layouts.Size());
for (const auto& json : layouts)
{
if (json != L"")
{
converted.emplace_back(WindowLayout::FromJson(json));
}
}
ApplicationState::SharedInstance().PersistedWindowLayouts(winrt::single_threaded_vector(std::move(converted)));
}
hstring AppLogic::GetWindowLayoutJson(LaunchPosition position)
{
if (_root != nullptr)
{
if (const auto layout = _root->GetWindowLayout())
{
layout.InitialPosition(position);
return WindowLayout::ToJson(layout);
}
}
return L"";
}
void AppLogic::IdentifyWindow()
{
if (_root)
@ -1459,8 +1515,17 @@ namespace winrt::TerminalApp::implementation
}
}
void AppLogic::SetPersistedLayoutIdx(const uint32_t idx)
{
if (_root)
{
_root->SetPersistedLayoutIdx(idx);
}
}
void AppLogic::SetNumberOfOpenWindows(const uint64_t num)
{
_numOpenWindows = num;
if (_root)
{
_root->SetNumberOfOpenWindows(num);

View File

@ -55,6 +55,8 @@ namespace winrt::TerminalApp::implementation
void Quit();
bool HasCommandlineArguments() const noexcept;
bool HasSettingsStartupActions() const noexcept;
int32_t SetStartupCommandline(array_view<const winrt::hstring> actions);
int32_t ExecuteCommandline(array_view<const winrt::hstring> actions, const winrt::hstring& cwd);
TerminalApp::FindTargetWindowResult FindTargetWindow(array_view<const winrt::hstring> actions);
@ -65,12 +67,16 @@ namespace winrt::TerminalApp::implementation
bool Fullscreen() const;
bool AlwaysOnTop() const;
bool ShouldUsePersistedLayout();
hstring GetWindowLayoutJson(Microsoft::Terminal::Settings::Model::LaunchPosition position);
void SaveWindowLayoutJsons(const Windows::Foundation::Collections::IVector<hstring>& layouts);
void IdentifyWindow();
void RenameFailed();
winrt::hstring WindowName();
void WindowName(const winrt::hstring& name);
uint64_t WindowId();
void WindowId(const uint64_t& id);
void SetPersistedLayoutIdx(const uint32_t idx);
void SetNumberOfOpenWindows(const uint64_t num);
bool IsQuakeWindow() const noexcept;
@ -91,7 +97,7 @@ namespace winrt::TerminalApp::implementation
void TitlebarClicked();
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
void WindowCloseButtonClicked();
void CloseWindow(Microsoft::Terminal::Settings::Model::LaunchPosition position);
winrt::TerminalApp::TaskbarState TaskbarState();
@ -123,6 +129,8 @@ namespace winrt::TerminalApp::implementation
HRESULT _settingsLoadedResult = S_OK;
bool _loadedInitialSettings = false;
uint64_t _numOpenWindows{ 0 };
std::shared_mutex _dialogLock;
::TerminalApp::AppCommandlineArgs _appArgs;
@ -175,6 +183,7 @@ namespace winrt::TerminalApp::implementation
FORWARDED_TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs, _root, RenameWindowRequested);
FORWARDED_TYPED_EVENT(IsQuakeWindowChanged, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, IsQuakeWindowChanged);
FORWARDED_TYPED_EVENT(SummonWindowRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, SummonWindowRequested);
FORWARDED_TYPED_EVENT(CloseRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, CloseRequested);
FORWARDED_TYPED_EVENT(OpenSystemMenu, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, OpenSystemMenu);
FORWARDED_TYPED_EVENT(QuitRequested, Windows::Foundation::IInspectable, Windows::Foundation::IInspectable, _root, QuitRequested);

View File

@ -34,6 +34,8 @@ namespace TerminalApp
void RunAsUwp();
Boolean IsElevated();
Boolean HasCommandlineArguments();
Boolean HasSettingsStartupActions();
Int32 SetStartupCommandline(String[] commands);
Int32 ExecuteCommandline(String[] commands, String cwd);
String ParseCommandlineMessage { get; };
@ -55,6 +57,7 @@ namespace TerminalApp
void IdentifyWindow();
String WindowName;
UInt64 WindowId;
void SetPersistedLayoutIdx(UInt32 idx);
void SetNumberOfOpenWindows(UInt64 num);
void RenameFailed();
Boolean IsQuakeWindow();
@ -69,10 +72,14 @@ namespace TerminalApp
Boolean GetInitialAlwaysOnTop();
Single CalcSnappedDimension(Boolean widthOrHeight, Single dimension);
void TitlebarClicked();
void WindowCloseButtonClicked();
void CloseWindow(Microsoft.Terminal.Settings.Model.LaunchPosition position);
TaskbarState TaskbarState{ get; };
Boolean ShouldUsePersistedLayout();
String GetWindowLayoutJson(Microsoft.Terminal.Settings.Model.LaunchPosition position);
void SaveWindowLayoutJsons(Windows.Foundation.Collections.IVector<String> layouts);
Boolean GetMinimizeToNotificationArea();
Boolean GetAlwaysShowNotificationIcon();
Boolean GetShowTitleInTitlebar();
@ -99,6 +106,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, Object> SettingsChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> IsQuakeWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> SummonWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> OpenSystemMenu;
event Windows.Foundation.TypedEventHandler<Object, Object> QuitRequested;
}

File diff suppressed because it is too large Load Diff

View File

@ -40,7 +40,8 @@ enum class Borders : int
Top = 0x1,
Bottom = 0x2,
Left = 0x4,
Right = 0x8
Right = 0x8,
All = 0xF
};
DEFINE_ENUM_FLAG_OPERATORS(Borders);
@ -58,7 +59,14 @@ public:
const winrt::Microsoft::Terminal::Control::TermControl& control,
const bool lastFocused = false);
Pane(std::shared_ptr<Pane> first,
std::shared_ptr<Pane> second,
const SplitState splitType,
const float splitPosition,
const bool lastFocused = false);
std::shared_ptr<Pane> GetActivePane();
winrt::Microsoft::Terminal::Control::TermControl GetLastFocusedTerminalControl();
winrt::Microsoft::Terminal::Control::TermControl GetTerminalControl();
winrt::Microsoft::Terminal::Settings::Model::Profile GetFocusedProfile();
@ -142,25 +150,43 @@ public:
// - true if the predicate returned true on any pane.
template<typename F>
//requires std::predicate<F, std::shared_ptr<Pane>>
bool WalkTree(F f)
auto WalkTree(F f) -> decltype(f(shared_from_this()))
{
if (f(shared_from_this()))
{
return true;
}
using R = std::invoke_result_t<F, std::shared_ptr<Pane>>;
static constexpr auto IsVoid = std::is_void_v<R>;
if (!_IsLeaf())
if constexpr (IsVoid)
{
return _firstChild->WalkTree(f) || _secondChild->WalkTree(f);
f(shared_from_this());
if (!_IsLeaf())
{
_firstChild->WalkTree(f);
_secondChild->WalkTree(f);
}
}
else
{
if (f(shared_from_this()))
{
return true;
}
return false;
if (!_IsLeaf())
{
return _firstChild->WalkTree(f) || _secondChild->WalkTree(f);
}
return false;
}
}
void CollectTaskbarStates(std::vector<winrt::TerminalApp::TaskbarState>& states);
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
DECLARE_EVENT(GotFocus, _GotFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
using gotFocusArgs = winrt::delegate<std::shared_ptr<Pane>, winrt::Windows::UI::Xaml::FocusState>;
DECLARE_EVENT(GotFocus, _GotFocusHandlers, gotFocusArgs);
DECLARE_EVENT(LostFocus, _LostFocusHandlers, winrt::delegate<std::shared_ptr<Pane>>);
DECLARE_EVENT(PaneRaiseBell, _PaneRaiseBellHandlers, winrt::Windows::Foundation::EventHandler<bool>);
DECLARE_EVENT(Detached, _PaneDetachedHandlers, winrt::delegate<std::shared_ptr<Pane>>);
@ -173,7 +199,8 @@ private:
struct LayoutSizeNode;
winrt::Windows::UI::Xaml::Controls::Grid _root{};
winrt::Windows::UI::Xaml::Controls::Border _border{};
winrt::Windows::UI::Xaml::Controls::Border _borderFirst{};
winrt::Windows::UI::Xaml::Controls::Border _borderSecond{};
winrt::Microsoft::Terminal::Control::TermControl _control{ nullptr };
winrt::Microsoft::Terminal::TerminalConnection::ConnectionState _connectionState{ winrt::Microsoft::Terminal::TerminalConnection::ConnectionState::NotConnected };
static winrt::Windows::UI::Xaml::Media::SolidColorBrush s_focusedBorderBrush;
@ -185,6 +212,7 @@ private:
float _desiredSplitPosition;
std::optional<uint32_t> _id;
std::weak_ptr<Pane> _parentChildPath{};
bool _lastActive{ false };
winrt::Microsoft::Terminal::Settings::Model::Profile _profile{ nullptr };
@ -205,6 +233,7 @@ private:
bool _IsLeaf() const noexcept;
bool _HasFocusedChild() const noexcept;
void _SetupChildCloseHandlers();
bool _HasChild(const std::shared_ptr<Pane> child);
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType,
const float splitSize,
@ -232,6 +261,7 @@ private:
void _CloseChild(const bool closeFirst, const bool isDetaching);
winrt::fire_and_forget _CloseChildRoutine(const bool closeFirst);
void _Focus();
void _FocusFirstChild();
void _ControlConnectionStateChangedHandler(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& /*args*/);
void _ControlWarningBellHandler(winrt::Windows::Foundation::IInspectable const& sender,

View File

@ -190,6 +190,9 @@
<data name="CloseWindowWarningTitle" xml:space="preserve">
<value>Do you want to close all tabs?</value>
</data>
<data name="MultiplePanes" xml:space="preserve">
<value>Multiple panes</value>
</data>
<data name="TabCloseSubMenu" xml:space="preserve">
<value>Close...</value>
</data>
@ -381,6 +384,9 @@
<data name="CmdFocusDesc" xml:space="preserve">
<value>Launch the window in focus mode</value>
</data>
<data name="CmdSavedLayoutArgDesc" xml:space="preserve">
<value>This parameter is an internal implementation detail and should not be used.</value>
</data>
<data name="CmdWindowTargetArgDesc" xml:space="preserve">
<value>Specify a terminal window to run the given commandline in. "0" always refers to the current window. </value>
</data>
@ -715,4 +721,7 @@
<data name="InfoBarDismissButton.Content" xml:space="preserve">
<value>Don't show again</value>
</data>
<data name="ElevationShield.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>This Terminal window is running as Admin</value>
</data>
</root>

View File

@ -19,6 +19,9 @@
#include "ColorHelper.h"
#include "DebugTapConnection.h"
#include "SettingsTab.h"
#include "..\TerminalSettingsModel\FileUtils.h"
#include <shlobj.h>
using namespace winrt;
using namespace winrt::Windows::Foundation::Collections;
@ -410,33 +413,45 @@ namespace winrt::TerminalApp::implementation
// - tab: tab to export
winrt::fire_and_forget TerminalPage::_ExportTab(const TerminalTab& tab)
{
// This will be used to set up the file picker "filter", to select .txt
// files by default.
static constexpr COMDLG_FILTERSPEC supportedFileTypes[] = {
{ L"Text Files (*.txt)", L"*.txt" },
{ L"All Files (*.*)", L"*.*" }
};
// An arbitrary GUID to associate with all instances of this
// dialog, so they all re-open in the same path as they were
// open before:
static constexpr winrt::guid clientGuidExportFile{ 0xF6AF20BB, 0x0800, 0x48E6, { 0xB0, 0x17, 0xA1, 0x4C, 0xD8, 0x73, 0xDD, 0x58 } };
try
{
if (const auto control{ tab.GetActiveTerminalControl() })
{
const FileSavePicker savePicker;
savePicker.as<IInitializeWithWindow>()->Initialize(*_hostingHwnd);
savePicker.SuggestedStartLocation(PickerLocationId::Downloads);
const auto fileChoices = single_threaded_vector<hstring>({ L".txt" });
savePicker.FileTypeChoices().Insert(RS_(L"PlainText"), fileChoices);
savePicker.SuggestedFileName(control.Title());
// GH#11356 - we can't use the UWP apis for writing the file,
// because they don't work elevated (shocker) So just use the
// shell32 file picker manually.
auto path = co_await SaveFilePicker(*_hostingHwnd, [control](auto&& dialog) {
THROW_IF_FAILED(dialog->SetClientGuid(clientGuidExportFile));
try
{
// Default to the Downloads folder
auto folderShellItem{ winrt::capture<IShellItem>(&SHGetKnownFolderItem, FOLDERID_Downloads, KF_FLAG_DEFAULT, nullptr) };
dialog->SetDefaultFolder(folderShellItem.get());
}
CATCH_LOG(); // non-fatal
THROW_IF_FAILED(dialog->SetFileTypes(ARRAYSIZE(supportedFileTypes), supportedFileTypes));
THROW_IF_FAILED(dialog->SetFileTypeIndex(1)); // the array is 1-indexed
THROW_IF_FAILED(dialog->SetDefaultExtension(L"txt"));
const StorageFile file = co_await savePicker.PickSaveFileAsync();
if (file != nullptr)
// Default to using the tab title as the file name
THROW_IF_FAILED(dialog->SetFileName((control.Title() + L".txt").c_str()));
});
if (!path.empty())
{
const auto buffer = control.ReadEntireBuffer();
CachedFileManager::DeferUpdates(file);
co_await FileIO::WriteTextAsync(file, buffer);
const auto status = co_await CachedFileManager::CompleteUpdatesAsync(file);
switch (status)
{
case FileUpdateStatus::Complete:
case FileUpdateStatus::CompleteAndRenamed:
_ShowControlNoticeDialog(RS_(L"NoticeInfo"), RS_(L"ExportSuccess"));
break;
default:
_ShowControlNoticeDialog(RS_(L"NoticeError"), RS_(L"ExportFailure"));
}
CascadiaSettings::ExportFile(path, buffer);
}
}
}
@ -501,7 +516,9 @@ namespace winrt::TerminalApp::implementation
{
// If we are supposed to save state, make sure we clear it out
// if the user manually closed all tabs.
if (!_maintainStateOnTabClose && ShouldUsePersistedLayout(_settings))
// Do this only if we are the last window; the monarch will notice
// we are missing and remove us that way otherwise.
if (!_maintainStateOnTabClose && ShouldUsePersistedLayout(_settings) && _numOpenWindows == 1)
{
auto state = ApplicationState::SharedInstance();
state.PersistedWindowLayouts(nullptr);
@ -734,31 +751,32 @@ namespace winrt::TerminalApp::implementation
{
_UnZoomIfNeeded();
auto pane = terminalTab->GetActivePane();
if (const auto pane{ terminalTab->GetActivePane() })
{
if (const auto control{ pane->GetTerminalControl() })
if (pane->ContainsReadOnly())
{
if (control.ReadOnly())
ContentDialogResult warningResult = co_await _ShowCloseReadOnlyDialog();
// If the user didn't explicitly click on close tab - leave
if (warningResult != ContentDialogResult::Primary)
{
ContentDialogResult warningResult = co_await _ShowCloseReadOnlyDialog();
// If the user didn't explicitly click on close tab - leave
if (warningResult != ContentDialogResult::Primary)
{
co_return;
}
// Clean read-only mode to prevent additional prompt if closing the pane triggers closing of a hosting tab
if (control.ReadOnly())
{
control.ToggleReadOnly();
}
co_return;
}
pane->Close();
// Clean read-only mode to prevent additional prompt if closing the pane triggers closing of a hosting tab
pane->WalkTree([](auto p) {
if (const auto control{ p->GetTerminalControl() })
{
if (control.ReadOnly())
{
control.ToggleReadOnly();
}
}
return false;
});
}
pane->Close();
}
}
else if (auto index{ _GetFocusedTabIndex() })

View File

@ -4,6 +4,7 @@
#pragma once
#include "winrt/Microsoft.UI.Xaml.Controls.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
#include "TabRowControl.g.h"
@ -16,6 +17,9 @@ namespace winrt::TerminalApp::implementation
void OnNewTabButtonClick(Windows::Foundation::IInspectable const& sender, Microsoft::UI::Xaml::Controls::SplitButtonClickEventArgs const& args);
void OnNewTabButtonDrop(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::DragEventArgs const& e);
void OnNewTabButtonDragOver(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::DragEventArgs const& e);
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
WINRT_OBSERVABLE_PROPERTY(bool, ShowElevationShield, _PropertyChangedHandlers, false);
};
}

View File

@ -3,9 +3,11 @@
namespace TerminalApp
{
[default_interface] runtimeclass TabRowControl : Windows.UI.Xaml.Controls.ContentPresenter
[default_interface] runtimeclass TabRowControl : Windows.UI.Xaml.Controls.ContentPresenter,
Windows.UI.Xaml.Data.INotifyPropertyChanged
{
TabRowControl();
Microsoft.UI.Xaml.Controls.TabView TabView { get; };
Boolean ShowElevationShield;
}
}

View File

@ -20,6 +20,17 @@
IsAddTabButtonVisible="false"
TabWidthMode="Equal">
<mux:TabView.TabStripHeader>
<!-- EA18 is the "Shield" glyph -->
<FontIcon x:Uid="ElevationShield"
Margin="9,4,0,0"
FontFamily="Segoe MDL2 Assets"
FontSize="16"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
Glyph="&#xEA18;"
Visibility="{x:Bind ShowElevationShield, Mode=OneWay}" />
</mux:TabView.TabStripHeader>
<mux:TabView.TabStripFooter>
<mux:SplitButton x:Name="NewTabButton"
x:Uid="NewTabSplitButton"

View File

@ -118,6 +118,29 @@ namespace winrt::TerminalApp::implementation
_systemRowsToScroll = _ReadSystemRowsToScroll();
}
bool TerminalPage::IsElevated() const 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 bool isElevated = []() {
// *** THIS IS A SINGLETON ***
auto result = false;
// GH#2455 - Make sure to try/catch calls to Application::Current,
// because that _won't_ be an instance of TerminalApp::App in the
// LocalTests
try
{
result = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsElevated();
}
CATCH_LOG();
return result;
}();
return isElevated;
}
void TerminalPage::Create()
{
// Hookup the key bindings
@ -128,19 +151,7 @@ namespace winrt::TerminalApp::implementation
_tabView = _tabRow.TabView();
_rearranging = false;
// GH#2455 - Make sure to try/catch calls to Application::Current,
// because that _won't_ be an instance of TerminalApp::App in the
// LocalTests
auto isElevated = false;
try
{
// GH#3581 - There's a platform limitation that causes us to crash when we rearrange tabs.
// Xaml tries to send a drag visual (to wit: a screenshot) to the drag hosting process,
// but that process is running at a different IL than us.
// For now, we're disabling elevated drag.
isElevated = ::winrt::Windows::UI::Xaml::Application::Current().as<::winrt::TerminalApp::App>().Logic().IsElevated();
}
CATCH_LOG();
const auto isElevated = IsElevated();
if (_settings.GlobalSettings().UseAcrylicInTabRow())
{
@ -267,6 +278,8 @@ namespace winrt::TerminalApp::implementation
// Setup mouse vanish attributes
SystemParametersInfoW(SPI_GETMOUSEVANISH, 0, &_shouldMouseVanish, false);
_tabRow.ShowElevationShield(IsElevated() && _settings.GlobalSettings().ShowAdminShield());
// Store cursor, so we can restore it, e.g., after mouse vanishing
// (we'll need to adapt this logic once we make cursor context aware)
try
@ -285,10 +298,37 @@ namespace winrt::TerminalApp::implementation
// - true if the ApplicationState should be used.
bool TerminalPage::ShouldUsePersistedLayout(CascadiaSettings& settings) const
{
// If the setting is enabled, and we are the only window.
// GH#5000 Until there is a separate state file for elevated sessions we should just not
// save at all while in an elevated window.
return Feature_PersistedWindowLayout::IsEnabled() &&
settings.GlobalSettings().FirstWindowPreference() == FirstWindowPreference::PersistedWindowLayout &&
_numOpenWindows == 1;
!IsElevated() &&
settings.GlobalSettings().FirstWindowPreference() == FirstWindowPreference::PersistedWindowLayout;
}
// Method Description;
// - Checks if the current window is configured to load a particular layout
// Arguments:
// - settings: The settings to use as this may be called before the page is
// fully initialized.
// Return Value:
// - non-null if there is a particular saved layout to use
std::optional<uint32_t> TerminalPage::LoadPersistedLayoutIdx(CascadiaSettings& settings) const
{
return ShouldUsePersistedLayout(settings) ? _loadFromPersistedLayoutIdx : std::nullopt;
}
WindowLayout TerminalPage::LoadPersistedLayout(CascadiaSettings& settings) const
{
if (const auto idx = LoadPersistedLayoutIdx(settings))
{
const auto i = idx.value();
const auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts();
if (layouts && layouts.Size() > i)
{
return layouts.GetAt(i);
}
}
return nullptr;
}
winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e)
@ -374,30 +414,13 @@ namespace winrt::TerminalApp::implementation
{
_startupState = StartupState::InStartup;
// If the user selected to save their tab layout, we are the first
// window opened, and wt was not run with any other arguments, then
// we should use the saved settings.
auto firstActionIsDefault = [](ActionAndArgs action) {
if (action.Action() != ShortcutAction::NewTab)
{
return false;
}
// If no commands were given, we will have default args
if (const auto args = action.Args().try_as<NewTabArgs>())
{
NewTerminalArgs defaultArgs{};
return args.TerminalArgs() == nullptr || args.TerminalArgs().Equals(defaultArgs);
}
return false;
};
if (ShouldUsePersistedLayout(_settings) && _startupActions.Size() == 1 && firstActionIsDefault(_startupActions.GetAt(0)))
// If we are provided with an index, the cases where we have
// commandline args and startup actions are already handled.
if (const auto layout = LoadPersistedLayout(_settings))
{
auto layouts = ApplicationState::SharedInstance().PersistedWindowLayouts();
if (layouts && layouts.Size() > 0 && layouts.GetAt(0).TabLayout() && layouts.GetAt(0).TabLayout().Size() > 0)
if (layout.TabLayout().Size() > 0)
{
_startupActions = layouts.GetAt(0).TabLayout();
_startupActions = layout.TabLayout();
}
}
@ -1276,12 +1299,19 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Saves the window position and tab layout to the application state
// - This does not create the InitialPosition field, that needs to be
// added externally.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TerminalPage::PersistWindowLayout()
// - the window layout
WindowLayout TerminalPage::GetWindowLayout()
{
if (_startupState != StartupState::Initialized)
{
return nullptr;
}
std::vector<ActionAndArgs> actions;
for (auto tab : _tabs)
@ -1289,7 +1319,7 @@ namespace winrt::TerminalApp::implementation
if (auto terminalTab = _GetTerminalTabImpl(tab))
{
auto tabActions = terminalTab->BuildStartupActions();
actions.insert(actions.end(), tabActions.begin(), tabActions.end());
actions.insert(actions.end(), std::make_move_iterator(tabActions.begin()), std::make_move_iterator(tabActions.end()));
}
else if (tab.try_as<SettingsTab>())
{
@ -1298,7 +1328,7 @@ namespace winrt::TerminalApp::implementation
OpenSettingsArgs args{ SettingsTarget::SettingsUI };
action.Args(args);
actions.push_back(action);
actions.emplace_back(std::move(action));
}
}
@ -1311,7 +1341,18 @@ namespace winrt::TerminalApp::implementation
SwitchToTabArgs switchToTabArgs{ idx.value() };
action.Args(switchToTabArgs);
actions.push_back(action);
actions.emplace_back(std::move(action));
}
// If the user set a custom name, save it
if (_WindowName != L"")
{
ActionAndArgs action;
action.Action(ShortcutAction::RenameWindow);
RenameWindowArgs args{ _WindowName };
action.Args(args);
actions.emplace_back(std::move(action));
}
WindowLayout layout{};
@ -1324,33 +1365,7 @@ namespace winrt::TerminalApp::implementation
layout.InitialSize(windowSize);
if (_hostingHwnd)
{
// Get the position of the current window. This includes the
// non-client already.
RECT window{};
GetWindowRect(_hostingHwnd.value(), &window);
// We want to remove the non-client area so calculate that.
// We don't have access to the (NonClient)IslandWindow directly so
// just replicate the logic.
const auto windowStyle = static_cast<DWORD>(GetWindowLong(_hostingHwnd.value(), GWL_STYLE));
auto dpi = GetDpiForWindow(_hostingHwnd.value());
RECT nonClientArea{};
LOG_IF_WIN32_BOOL_FALSE(AdjustWindowRectExForDpi(&nonClientArea, windowStyle, false, 0, dpi));
// The nonClientArea adjustment is negative, so subtract that out.
// This way we save the user-visible location of the terminal.
LaunchPosition pos{};
pos.X = window.left - nonClientArea.left;
pos.Y = window.top;
layout.InitialPosition(pos);
}
auto state = ApplicationState::SharedInstance();
state.PersistedWindowLayouts(winrt::single_threaded_vector<WindowLayout>({ layout }));
return layout;
}
// Method Description:
@ -1379,8 +1394,9 @@ namespace winrt::TerminalApp::implementation
if (ShouldUsePersistedLayout(_settings))
{
PersistWindowLayout();
// don't delete the ApplicationState when all of the tabs are removed.
// Don't delete the ApplicationState when all of the tabs are removed.
// If there is still a monarch living they will get the event that
// a window closed and trigger a new save without this window.
_maintainStateOnTabClose = true;
}
@ -2242,6 +2258,8 @@ namespace winrt::TerminalApp::implementation
// enabled application-wide, so we don't need to check it each time we
// want to create an animation.
WUX::Media::Animation::Timeline::AllowDependentAnimations(!_settings.GlobalSettings().DisableAnimations());
_tabRow.ShowElevationShield(IsElevated() && _settings.GlobalSettings().ShowAdminShield());
}
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
@ -3091,6 +3109,11 @@ namespace winrt::TerminalApp::implementation
}
}
void TerminalPage::SetPersistedLayoutIdx(const uint32_t idx)
{
_loadFromPersistedLayoutIdx = idx;
}
void TerminalPage::SetNumberOfOpenWindows(const uint64_t num)
{
_numOpenWindows = num;

View File

@ -59,6 +59,9 @@ namespace winrt::TerminalApp::implementation
void Create();
bool ShouldUsePersistedLayout(Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const;
std::optional<uint32_t> LoadPersistedLayoutIdx(Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const;
winrt::Microsoft::Terminal::Settings::Model::WindowLayout LoadPersistedLayout(Microsoft::Terminal::Settings::Model::CascadiaSettings& settings) const;
Microsoft::Terminal::Settings::Model::WindowLayout GetWindowLayout();
winrt::fire_and_forget NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e);
@ -82,7 +85,6 @@ namespace winrt::TerminalApp::implementation
bool AlwaysOnTop() const;
void SetStartupActions(std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs>& actions);
void PersistWindowLayout();
void SetInboundListener(bool isEmbedding);
static std::vector<Microsoft::Terminal::Settings::Model::ActionAndArgs> ConvertExecuteCommandlineToActions(const Microsoft::Terminal::Settings::Model::ExecuteCommandlineArgs& args);
@ -111,10 +113,12 @@ namespace winrt::TerminalApp::implementation
void WindowId(const uint64_t& value);
void SetNumberOfOpenWindows(const uint64_t value);
void SetPersistedLayoutIdx(const uint32_t value);
winrt::hstring WindowIdForDisplay() const noexcept;
winrt::hstring WindowNameForDisplay() const noexcept;
bool IsQuakeWindow() const noexcept;
bool IsElevated() const noexcept;
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
@ -132,6 +136,7 @@ namespace winrt::TerminalApp::implementation
TYPED_EVENT(RenameWindowRequested, Windows::Foundation::IInspectable, winrt::TerminalApp::RenameWindowRequestedArgs);
TYPED_EVENT(IsQuakeWindowChanged, IInspectable, IInspectable);
TYPED_EVENT(SummonWindowRequested, IInspectable, IInspectable);
TYPED_EVENT(CloseRequested, IInspectable, IInspectable);
TYPED_EVENT(OpenSystemMenu, IInspectable, IInspectable);
TYPED_EVENT(QuitRequested, IInspectable, IInspectable);
@ -165,6 +170,7 @@ namespace winrt::TerminalApp::implementation
bool _isAlwaysOnTop{ false };
winrt::hstring _WindowName{};
uint64_t _WindowId{ 0 };
std::optional<uint32_t> _loadFromPersistedLayoutIdx{};
uint64_t _numOpenWindows{ 0 };
bool _maintainStateOnTabClose{ false };
@ -256,6 +262,26 @@ namespace winrt::TerminalApp::implementation
bool _SwapPane(const Microsoft::Terminal::Settings::Model::FocusDirection& direction);
bool _MovePane(const uint32_t tabIdx);
template<typename F>
bool _ApplyToActiveControls(F f)
{
if (const auto tab{ _GetFocusedTabImpl() })
{
if (const auto activePane = tab->GetActivePane())
{
activePane->WalkTree([&](auto p) {
if (const auto& control{ p->GetTerminalControl() })
{
f(control);
}
});
return true;
}
}
return false;
}
winrt::Microsoft::Terminal::Control::TermControl _GetActiveControl();
std::optional<uint32_t> _GetFocusedTabIndex() const noexcept;
TerminalApp::TabBase _GetFocusedTab() const noexcept;
@ -359,7 +385,7 @@ namespace winrt::TerminalApp::implementation
void _EndPreviewColorScheme();
void _PreviewColorScheme(const Microsoft::Terminal::Settings::Model::SetColorSchemeArgs& args);
winrt::Microsoft::Terminal::Settings::Model::Command _lastPreviewedCommand{ nullptr };
winrt::Microsoft::Terminal::Settings::Model::TerminalSettings _originalSettings{ nullptr };
std::vector<std::function<void()>> _restorePreviewFuncs{};
HRESULT _OnNewConnection(winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection connection);
void _HandleToggleInboundPty(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args);

View File

@ -33,7 +33,6 @@ namespace TerminalApp
UInt64 WindowId;
String WindowNameForDisplay { get; };
String WindowIdForDisplay { get; };
void SetNumberOfOpenWindows(UInt64 num);
void RenameFailed();
Boolean IsQuakeWindow();
@ -58,6 +57,7 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<Object, RenameWindowRequestedArgs> RenameWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> IsQuakeWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> SummonWindowRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseRequested;
event Windows.Foundation.TypedEventHandler<Object, Object> OpenSystemMenu;
}
}

View File

@ -67,8 +67,11 @@ namespace winrt::TerminalApp::implementation
_rootPane->FocusPane(firstId);
_activePane = _rootPane->GetActivePane();
}
// Set the active control
_mruPanes.insert(_mruPanes.begin(), _activePane->Id().value());
// If the focused pane is a leaf, add it to the MRU panes
if (const auto id = _activePane->Id())
{
_mruPanes.insert(_mruPanes.begin(), id.value());
}
_Setup();
}
@ -180,8 +183,8 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Returns nullptr if no children of this tab were the last control to be
// focused, or the TermControl that _was_ the last control to be focused (if
// there was one).
// focused, the active control of the current pane, or the last active child control
// of the active pane if it is a parent.
// - This control might not currently be focused, if the tab itself is not
// currently focused.
// Arguments:
@ -193,7 +196,7 @@ namespace winrt::TerminalApp::implementation
{
if (_activePane)
{
return _activePane->GetTerminalControl();
return _activePane->GetLastFocusedTerminalControl();
}
return nullptr;
}
@ -390,6 +393,10 @@ namespace winrt::TerminalApp::implementation
{
return _runtimeTabText;
}
if (!_activePane->_IsLeaf())
{
return RS_(L"MultiplePanes");
}
const auto lastFocusedControl = GetActiveTerminalControl();
return lastFocusedControl ? lastFocusedControl.Title() : L"";
}
@ -450,12 +457,25 @@ namespace winrt::TerminalApp::implementation
// 1 for the child after the first split.
auto state = _rootPane->BuildStartupActions(0, 1);
ActionAndArgs newTabAction{};
newTabAction.Action(ShortcutAction::NewTab);
NewTabArgs newTabArgs{ state.firstPane->GetTerminalArgsForPane() };
newTabAction.Args(newTabArgs);
{
ActionAndArgs newTabAction{};
newTabAction.Action(ShortcutAction::NewTab);
NewTabArgs newTabArgs{ state.firstPane->GetTerminalArgsForPane() };
newTabAction.Args(newTabArgs);
state.args.emplace(state.args.begin(), std::move(newTabAction));
state.args.emplace(state.args.begin(), std::move(newTabAction));
}
if (_runtimeTabColor)
{
ActionAndArgs setColorAction{};
setColorAction.Action(ShortcutAction::SetTabColor);
SetTabColorArgs setColorArgs{ _runtimeTabColor.value() };
setColorAction.Args(setColorArgs);
state.args.emplace_back(std::move(setColorAction));
}
// If we only have one arg, we only have 1 pane so we don't need any
// special focus logic
@ -501,19 +521,14 @@ namespace winrt::TerminalApp::implementation
// either the first or second child, but this will always return the
// original pane first.
auto [original, newPane] = _activePane->Split(splitType, splitSize, profile, control);
// The active pane has an id if it is a leaf
if (activePaneId)
{
original->Id(activePaneId.value());
newPane->Id(_nextPaneId);
++_nextPaneId;
}
else
{
original->Id(_nextPaneId);
++_nextPaneId;
newPane->Id(_nextPaneId);
++_nextPaneId;
}
newPane->Id(_nextPaneId);
++_nextPaneId;
_activePane = original;
// Add a event handlers to the new panes' GotFocus event. When the pane
@ -538,8 +553,8 @@ namespace winrt::TerminalApp::implementation
// - The removed pane, if the remove succeeded.
std::shared_ptr<Pane> TerminalTab::DetachPane()
{
// if we only have one pane, remove it entirely
// and close this tab
// if we only have one pane, or the focused pane is the root, remove it
// entirely and close this tab
if (_rootPane == _activePane)
{
return DetachRoot();
@ -614,16 +629,12 @@ namespace winrt::TerminalApp::implementation
// Add the new pane as an automatic split on the active pane.
auto first = _activePane->AttachPane(pane, SplitDirection::Automatic);
// under current assumptions this condition should always be true.
// This will be true if the original _activePane is a leaf pane.
// If it is a parent pane then we don't want to set an ID on it.
if (previousId)
{
first->Id(previousId.value());
}
else
{
first->Id(_nextPaneId);
++_nextPaneId;
}
// Update with event handlers on the new child.
_activePane = first;
@ -699,7 +710,10 @@ namespace winrt::TerminalApp::implementation
// throughout the entire tree.
if (const auto newFocus = _rootPane->NavigateDirection(_activePane, direction, _mruPanes))
{
// Mark that we want the active pane to changed
_changingActivePane = true;
const auto res = _rootPane->FocusPane(newFocus);
_changingActivePane = false;
if (_zoomedPane)
{
@ -722,11 +736,22 @@ namespace winrt::TerminalApp::implementation
// - true if two panes were swapped.
bool TerminalTab::SwapPane(const FocusDirection& direction)
{
// You cannot swap panes with the parent/child pane because of the
// circular reference.
if (direction == FocusDirection::Parent || direction == FocusDirection::Child)
{
return false;
}
// NOTE: This _must_ be called on the root pane, so that it can propagate
// throughout the entire tree.
if (auto neighbor = _rootPane->NavigateDirection(_activePane, direction, _mruPanes))
{
return _rootPane->SwapPanes(_activePane, neighbor);
// SwapPanes will refocus the terminal to make sure that it has focus
// even after moving.
_changingActivePane = true;
const auto res = _rootPane->SwapPanes(_activePane, neighbor);
_changingActivePane = false;
return res;
}
return false;
@ -734,7 +759,10 @@ namespace winrt::TerminalApp::implementation
bool TerminalTab::FocusPane(const uint32_t id)
{
return _rootPane->FocusPane(id);
_changingActivePane = true;
const auto res = _rootPane->FocusPane(id);
_changingActivePane = false;
return res;
}
// Method Description:
@ -1027,7 +1055,7 @@ namespace winrt::TerminalApp::implementation
auto weakThis{ get_weak() };
std::weak_ptr<Pane> weakPane{ pane };
auto gotFocusToken = pane->GotFocus([weakThis](std::shared_ptr<Pane> sender) {
auto gotFocusToken = pane->GotFocus([weakThis](std::shared_ptr<Pane> sender, WUX::FocusState focus) {
// Do nothing if the Tab's lifetime is expired or pane isn't new.
auto tab{ weakThis.get() };
@ -1035,8 +1063,20 @@ namespace winrt::TerminalApp::implementation
{
if (sender != tab->_activePane)
{
tab->_UpdateActivePane(sender);
tab->_RecalculateAndApplyTabColor();
auto senderIsChild = tab->_activePane->_HasChild(sender);
// Only move focus if we the program moved focus, or the
// user moved with their mouse. This is a problem because a
// pane isn't a control itself, and if we have the parent
// focused we are fine if the terminal control is focused,
// but we don't want to update the active pane.
if (!senderIsChild ||
(focus == WUX::FocusState::Programmatic && tab->_changingActivePane) ||
focus == WUX::FocusState::Pointer)
{
tab->_UpdateActivePane(sender);
tab->_RecalculateAndApplyTabColor();
}
}
tab->_focusState = WUX::FocusState::Programmatic;
// This tab has gained focus, remove the bell indicator if it is active
@ -1071,8 +1111,19 @@ namespace winrt::TerminalApp::implementation
tab->Content(tab->_rootPane->GetRootElement());
tab->ExitZoom();
}
if (auto pane = weakPane.lock())
{
// When a parent pane is selected, but one of its children
// close out under it we still need to update title/focus information
// but the GotFocus handler will rightly see that the _activePane
// did not actually change. Triggering
if (pane != tab->_activePane && !tab->_activePane->_IsLeaf())
{
co_await winrt::resume_foreground(tab->Content().Dispatcher());
tab->_UpdateActivePane(tab->_activePane);
}
for (auto i = tab->_mruPanes.begin(); i != tab->_mruPanes.end(); ++i)
{
if (*i == pane->Id())
@ -1290,11 +1341,13 @@ namespace winrt::TerminalApp::implementation
// - The tab's color, if any
std::optional<winrt::Windows::UI::Color> TerminalTab::GetTabColor()
{
const auto currControlColor{ GetActiveTerminalControl().TabColor() };
std::optional<winrt::Windows::UI::Color> controlTabColor;
if (currControlColor != nullptr)
if (const auto& control = GetActiveTerminalControl())
{
controlTabColor = currControlColor.Value();
if (const auto color = control.TabColor())
{
controlTabColor = color.Value();
}
}
// A Tab's color will be the result of layering a variety of sources,
@ -1407,7 +1460,16 @@ namespace winrt::TerminalApp::implementation
// TabViewItem().Background() only sets the color of the tab background
// when the TabViewItem is unselected. So we still need to set the other
// properties ourselves.
TabViewItem().Background(deselectedTabBrush);
//
// GH#11294: DESPITE the fact that there's a Background() API that we
// could just call like:
//
// TabViewItem().Background(deselectedTabBrush);
//
// We actually can't, because it will make the part of the tab that
// doesn't contain the text totally transparent to hit tests. So we
// actually _do_ still need to set TabViewItemHeaderBackground manually.
TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackground"), deselectedTabBrush);
TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), selectedTabBrush);
TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPointerOver"), hoverTabBrush);
TabViewItem().Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundPressed"), selectedTabBrush);
@ -1453,6 +1515,7 @@ namespace winrt::TerminalApp::implementation
void TerminalTab::_ClearTabBackgroundColor()
{
winrt::hstring keys[] = {
L"TabViewItemHeaderBackground",
L"TabViewItemHeaderBackgroundSelected",
L"TabViewItemHeaderBackgroundPointerOver",
L"TabViewItemHeaderForeground",
@ -1473,9 +1536,6 @@ namespace winrt::TerminalApp::implementation
}
}
// Clear out the Background.
TabViewItem().Background(nullptr);
_RefreshVisualState();
_colorCleared();
}
@ -1585,6 +1645,11 @@ namespace winrt::TerminalApp::implementation
void TerminalTab::EnterZoom()
{
// Clear the content first, because with parent focusing it is possible
// to zoom the root pane, but setting the content will not trigger the
// property changed event since it is the same and you would end up with
// an empty tab.
Content(nullptr);
_zoomedPane = _activePane;
_rootPane->Maximize(_zoomedPane);
// Update the tab header to show the magnifying glass
@ -1593,6 +1658,7 @@ namespace winrt::TerminalApp::implementation
}
void TerminalTab::ExitZoom()
{
Content(nullptr);
_rootPane->Restore(_zoomedPane);
_zoomedPane = nullptr;
// Update the tab header to hide the magnifying glass
@ -1607,13 +1673,34 @@ namespace winrt::TerminalApp::implementation
// Method Description:
// - Toggle read-only mode on the active pane
// - If a parent pane is selected, this will ensure that all children have
// the same read-only status.
void TerminalTab::TogglePaneReadOnly()
{
auto control = GetActiveTerminalControl();
if (control)
{
control.ToggleReadOnly();
}
auto hasReadOnly = false;
auto allReadOnly = true;
_activePane->WalkTree([&](auto p) {
if (const auto& control{ p->GetTerminalControl() })
{
hasReadOnly |= control.ReadOnly();
allReadOnly &= control.ReadOnly();
}
});
_activePane->WalkTree([&](auto p) {
if (const auto& control{ p->GetTerminalControl() })
{
// If all controls have the same read only state then just toggle
if (allReadOnly || !hasReadOnly)
{
control.ToggleReadOnly();
}
// otherwise set to all read only.
else if (!control.ReadOnly())
{
control.ToggleReadOnly();
}
}
});
}
// Method Description:

View File

@ -139,6 +139,7 @@ namespace winrt::TerminalApp::implementation
bool _receivedKeyDown{ false };
bool _iconHidden{ false };
bool _changingActivePane{ false };
winrt::hstring _runtimeTabText{};
bool _inRename{ false };

View File

@ -274,10 +274,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_updateAntiAliasingMode(_renderEngine.get());
// GH#5098: Inform the engine of the opacity of the default text background.
if (_settings.UseAcrylic())
{
_renderEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(_settings.Opacity()));
}
// GH#11315: Always do this, even if they don't have acrylic on.
_renderEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(_settings.Opacity()));
THROW_IF_FAILED(_renderEngine->Enable());
@ -359,21 +357,28 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const ControlKeyStates modifiers,
const bool keyDown)
{
// When there is a selection active, escape should clear it and NOT flow through
// to the terminal. With any other keypress, it should clear the selection AND
// flow through to the terminal.
// Update the selection, if it's present
// GH#6423 - don't dismiss selection if the key that was pressed was a
// modifier key. We'll wait for a real keystroke to dismiss the
// GH #7395 - don't dismiss selection when taking PrintScreen
// selection.
// GH#8522, GH#3758 - Only dismiss the selection on key _down_. If we
// dismiss on key up, then there's chance that we'll immediately dismiss
// GH#8522, GH#3758 - Only modify the selection on key _down_. If we
// modify on key up, then there's chance that we'll immediately dismiss
// a selection created by an action bound to a keydown.
if (HasSelection() &&
!KeyEvent::IsModifierKey(vkey) &&
vkey != VK_SNAPSHOT &&
keyDown)
{
// try to update the selection
if (const auto updateSlnParams{ ::Terminal::ConvertKeyEventToUpdateSelectionParams(modifiers, vkey) })
{
auto lock = _terminal->LockForWriting();
_terminal->UpdateSelection(updateSlnParams->first, updateSlnParams->second);
_renderer->TriggerSelection();
return true;
}
// GH#8791 - don't dismiss selection if Windows key was also pressed as a key-combination.
if (!modifiers.IsWinPressed())
{
@ -381,6 +386,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_renderer->TriggerSelection();
}
// When there is a selection active, escape should clear it and NOT flow through
// to the terminal. With any other keypress, it should clear the selection AND
// flow through to the terminal.
if (vkey == VK_ESCAPE)
{
return true;
@ -459,6 +467,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
_renderEngine->ToggleShaderEffects();
}
// Always redraw after toggling effects. This way even if the control
// does not have focus it will update immediately.
_renderer->TriggerRedrawAll();
}
// Method Description:
@ -1399,18 +1410,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// handle ALT key
_terminal->SetBlockSelection(altEnabled);
::Terminal::SelectionExpansionMode mode = ::Terminal::SelectionExpansionMode::Cell;
::Terminal::SelectionExpansion mode = ::Terminal::SelectionExpansion::Char;
if (numberOfClicks == 1)
{
mode = ::Terminal::SelectionExpansionMode::Cell;
mode = ::Terminal::SelectionExpansion::Char;
}
else if (numberOfClicks == 2)
{
mode = ::Terminal::SelectionExpansionMode::Word;
mode = ::Terminal::SelectionExpansion::Word;
}
else if (numberOfClicks == 3)
{
mode = ::Terminal::SelectionExpansionMode::Line;
mode = ::Terminal::SelectionExpansion::Line;
}
// Update the selection appropriately
@ -1435,7 +1446,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->SetSelectionEnd(terminalPosition, mode);
selectionNeedsToBeCopied = true;
}
else if (mode != ::Terminal::SelectionExpansionMode::Cell || shiftEnabled)
else if (mode != ::Terminal::SelectionExpansion::Char || shiftEnabled)
{
// If we are handling a double / triple-click or shift+single click
// we establish selection using the selected mode
@ -1534,5 +1545,4 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return hstring(ss.str());
}
}

View File

@ -214,12 +214,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
else if (_canSendVTMouseInput(modifiers))
{
const auto adjustment = _core->ScrollOffset() > 0 ? _core->BufferHeight() - _core->ScrollOffset() - _core->ViewHeight() : 0;
// If the click happened outside the active region, just don't send any mouse event
if (const auto adjustedY = terminalPosition.y() - adjustment; adjustedY >= 0)
{
_core->SendMouseEvent({ terminalPosition.x(), adjustedY }, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
}
_sendMouseEventHelper(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
}
else if (WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown))
{
@ -287,7 +282,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Short-circuit isReadOnly check to avoid warning dialog
if (focused && !_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers))
{
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
_sendMouseEventHelper(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
}
// GH#4603 - don't modify the selection if the pointer press didn't
// actually start _in_ the control bounds. Case in point - someone drags
@ -370,7 +365,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Short-circuit isReadOnly check to avoid warning dialog
if (!_core->IsInReadOnlyMode() && _canSendVTMouseInput(modifiers))
{
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
_sendMouseEventHelper(terminalPosition, pointerUpdateKind, modifiers, 0, buttonState);
return;
}
@ -420,11 +415,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// here with a PointerPoint. However, as of #979, we don't have a
// PointerPoint to work with. So, we're just going to do a
// mousewheel event manually
return _core->SendMouseEvent(terminalPosition,
return _sendMouseEventHelper(terminalPosition,
WM_MOUSEWHEEL,
modifiers,
::base::saturated_cast<short>(delta),
toInternalMouseState(buttonState));
buttonState);
}
const auto ctrlPressed = modifiers.IsCtrlPressed();
@ -600,6 +595,21 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return til::point{ pixelPosition / fontSize };
}
bool ControlInteractivity::_sendMouseEventHelper(const til::point terminalPosition,
const unsigned int pointerUpdateKind,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const SHORT wheelDelta,
Control::MouseButtonState buttonState)
{
const auto adjustment = _core->ScrollOffset() > 0 ? _core->BufferHeight() - _core->ScrollOffset() - _core->ViewHeight() : 0;
// If the click happened outside the active region, just don't send any mouse event
if (const auto adjustedY = terminalPosition.y() - adjustment; adjustedY >= 0)
{
return _core->SendMouseEvent({ terminalPosition.x(), adjustedY }, pointerUpdateKind, modifiers, wheelDelta, toInternalMouseState(buttonState));
}
return false;
}
// Method Description:
// - Creates an automation peer for the Terminal Control, enabling
// accessibility on our control.

View File

@ -142,6 +142,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void _sendPastedTextToConnection(std::wstring_view wstr);
til::point _getTerminalPosition(const til::point& pixelPosition);
bool _sendMouseEventHelper(const til::point terminalPosition,
const unsigned int pointerUpdateKind,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
const SHORT wheelDelta,
Control::MouseButtonState buttonState);
friend class ControlUnitTests::ControlCoreTests;
friend class ControlUnitTests::ControlInteractivityTests;
};

View File

@ -459,7 +459,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
// GH#5098: Inform the engine of the new opacity of the default text background.
_core.SetBackgroundOpacity(::base::saturated_cast<float>(appearance.Opacity()));
_core.SetBackgroundOpacity(appearance.Opacity());
}
else
{
@ -848,6 +848,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return;
}
const auto keyStatus = e.KeyStatus();
const auto vkey = gsl::narrow_cast<WORD>(e.OriginalKey());
const auto scanCode = gsl::narrow_cast<WORD>(keyStatus.ScanCode);
auto modifiers = _GetPressedModifierKeys();
// GH#11076:
// For some weird reason we sometimes receive a WM_KEYDOWN
// message without vkey or scanCode if a user drags a tab.
// The KeyChord constructor has a debug assertion ensuring that all KeyChord
// either have a valid vkey/scanCode. This is important, because this prevents
// accidential insertion of invalid KeyChords into classes like ActionMap.
if (!vkey && !scanCode)
{
e.Handled(true);
return;
}
// Mark the event as handled and do nothing if we're closing, or the key
// was the Windows key.
//
@ -856,19 +873,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// win32-input-mode, then we'll send all these keystrokes to the
// terminal - it's smart enough to ignore the keys it doesn't care
// about.
if (_IsClosing() ||
e.OriginalKey() == VirtualKey::LeftWindows ||
e.OriginalKey() == VirtualKey::RightWindows)
if (_IsClosing() || vkey == VK_LWIN || vkey == VK_RWIN)
{
e.Handled(true);
return;
}
auto modifiers = _GetPressedModifierKeys();
const auto vkey = gsl::narrow_cast<WORD>(e.OriginalKey());
const auto scanCode = gsl::narrow_cast<WORD>(e.KeyStatus().ScanCode);
// Short-circuit isReadOnly check to avoid warning dialog
if (_core.IsInReadOnlyMode())
{
@ -876,7 +886,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
return;
}
if (e.KeyStatus().IsExtendedKey)
if (keyStatus.IsExtendedKey)
{
modifiers |= ControlKeyStates::EnhancedKey;
}
@ -886,8 +896,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// will be sent through the TSFInputControl. See GH#1401 for more
// details
if (modifiers.IsAltPressed() &&
(e.OriginalKey() >= VirtualKey::NumberPad0 && e.OriginalKey() <= VirtualKey::NumberPad9))
(vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9))
{
e.Handled(true);
return;
@ -917,7 +926,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Manually prevent keyboard navigation with tab. We want to send tab to
// the terminal, and we don't want to be able to escape focus of the
// control with tab.
e.Handled(e.OriginalKey() == VirtualKey::Tab);
e.Handled(vkey == VK_TAB);
}
// Method Description:

View File

@ -849,7 +849,13 @@ WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept
// will release this lock when it's destructed.
[[nodiscard]] std::unique_lock<til::ticket_lock> Terminal::LockForReading()
{
#ifdef NDEBUG
return std::unique_lock{ _readWriteLock };
#else
auto lock = std::unique_lock{ _readWriteLock };
_lastLocker = GetCurrentThreadId();
return lock;
#endif
}
// Method Description:
@ -859,7 +865,13 @@ WORD Terminal::_TakeVirtualKeyFromLastKeyEvent(const WORD scanCode) noexcept
// will release this lock when it's destructed.
[[nodiscard]] std::unique_lock<til::ticket_lock> Terminal::LockForWriting()
{
#ifdef NDEBUG
return std::unique_lock{ _readWriteLock };
#else
auto lock = std::unique_lock{ _readWriteLock };
_lastLocker = GetCurrentThreadId();
return lock;
#endif
}
Viewport Terminal::_GetMutableViewport() const noexcept

View File

@ -199,6 +199,7 @@ public:
const COORD GetSelectionEnd() const noexcept override;
const std::wstring_view GetConsoleTitle() const noexcept override;
void ColorSelection(const COORD coordSelectionStart, const COORD coordSelectionEnd, const TextAttribute) override;
const bool IsUiaDataInitialized() const noexcept override;
#pragma endregion
void SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept;
@ -227,16 +228,30 @@ public:
#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
enum class SelectionExpansionMode
enum class SelectionDirection
{
Cell,
Word,
Line
Left,
Right,
Up,
Down
};
void MultiClickSelection(const COORD viewportPos, SelectionExpansionMode expansionMode);
enum class SelectionExpansion
{
Char,
Word,
Line, // Mouse selection only!
Viewport,
Buffer
};
void MultiClickSelection(const COORD viewportPos, SelectionExpansion expansionMode);
void SetSelectionAnchor(const COORD position);
void SetSelectionEnd(const COORD position, std::optional<SelectionExpansionMode> newExpansionMode = std::nullopt);
void SetSelectionEnd(const COORD position, std::optional<SelectionExpansion> newExpansionMode = std::nullopt);
void SetBlockSelection(const bool isEnabled) noexcept;
void UpdateSelection(SelectionDirection direction, SelectionExpansion mode);
using UpdateSelectionParams = std::optional<std::pair<SelectionDirection, SelectionExpansion>>;
static UpdateSelectionParams ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey);
const TextBuffer::TextAndColor RetrieveSelectedTextFromBuffer(bool trimTrailingWhitespace);
#pragma endregion
@ -254,6 +269,9 @@ private:
// But we can abuse the fact that the surrounding members rarely change and are huge
// (std::function is like 64 bytes) to create some natural padding without wasting space.
til::ticket_lock _readWriteLock;
#ifndef NDEBUG
DWORD _lastLocker;
#endif
std::function<void(const int, const int, const int)> _pfnScrollPositionChanged;
std::function<void(const til::color)> _pfnBackgroundColorChanged;
@ -308,7 +326,7 @@ private:
std::optional<SelectionAnchors> _selection;
bool _blockSelection;
std::wstring _wordDelimiters;
SelectionExpansionMode _multiClickSelectionMode;
SelectionExpansion _multiClickSelectionMode;
#pragma endregion
// TODO: These members are not shared by an alt-buffer. They should be
@ -375,6 +393,10 @@ private:
std::pair<COORD, COORD> _PivotSelection(const COORD targetPos, bool& targetStart) const;
std::pair<COORD, COORD> _ExpandSelectionAnchors(std::pair<COORD, COORD> anchors) const;
COORD _ConvertToBufferCell(const COORD viewportPos) const;
void _MoveByChar(SelectionDirection direction, COORD& pos);
void _MoveByWord(SelectionDirection direction, COORD& pos);
void _MoveByViewport(SelectionDirection direction, COORD& pos);
void _MoveByBuffer(SelectionDirection direction, COORD& pos);
#pragma endregion
Microsoft::Console::VirtualTerminal::SgrStack _sgrStack;

View File

@ -100,8 +100,8 @@ const bool Terminal::IsBlockSelection() const noexcept
// - Perform a multi-click selection at viewportPos expanding according to the expansionMode
// Arguments:
// - viewportPos: the (x,y) coordinate on the visible viewport
// - expansionMode: the SelectionExpansionMode to dictate the boundaries of the selection anchors
void Terminal::MultiClickSelection(const COORD viewportPos, SelectionExpansionMode expansionMode)
// - expansionMode: the SelectionExpansion to dictate the boundaries of the selection anchors
void Terminal::MultiClickSelection(const COORD viewportPos, SelectionExpansion expansionMode)
{
// set the selection pivot to expand the selection using SetSelectionEnd()
_selection = SelectionAnchors{};
@ -124,7 +124,7 @@ void Terminal::SetSelectionAnchor(const COORD viewportPos)
_selection = SelectionAnchors{};
_selection->pivot = _ConvertToBufferCell(viewportPos);
_multiClickSelectionMode = SelectionExpansionMode::Cell;
_multiClickSelectionMode = SelectionExpansion::Char;
SetSelectionEnd(viewportPos);
_selection->start = _selection->pivot;
@ -136,7 +136,7 @@ void Terminal::SetSelectionAnchor(const COORD viewportPos)
// Arguments:
// - viewportPos: the (x,y) coordinate on the visible viewport
// - newExpansionMode: overwrites the _multiClickSelectionMode for this function call. Used for ShiftClick
void Terminal::SetSelectionEnd(const COORD viewportPos, std::optional<SelectionExpansionMode> newExpansionMode)
void Terminal::SetSelectionEnd(const COORD viewportPos, std::optional<SelectionExpansion> newExpansionMode)
{
if (!_selection.has_value())
{
@ -210,15 +210,15 @@ std::pair<COORD, COORD> Terminal::_ExpandSelectionAnchors(std::pair<COORD, COORD
const auto bufferSize = _buffer->GetSize();
switch (_multiClickSelectionMode)
{
case SelectionExpansionMode::Line:
case SelectionExpansion::Line:
start = { bufferSize.Left(), start.Y };
end = { bufferSize.RightInclusive(), end.Y };
break;
case SelectionExpansionMode::Word:
case SelectionExpansion::Word:
start = _buffer->GetWordStart(start, _wordDelimiters);
end = _buffer->GetWordEnd(end, _wordDelimiters);
break;
case SelectionExpansionMode::Cell:
case SelectionExpansion::Char:
default:
// no expansion is necessary
break;
@ -235,6 +235,229 @@ void Terminal::SetBlockSelection(const bool isEnabled) noexcept
_blockSelection = isEnabled;
}
Terminal::UpdateSelectionParams Terminal::ConvertKeyEventToUpdateSelectionParams(const ControlKeyStates mods, const WORD vkey)
{
if (mods.IsShiftPressed() && !mods.IsAltPressed())
{
if (mods.IsCtrlPressed())
{
// Ctrl + Shift + _
switch (vkey)
{
case VK_LEFT:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Left, SelectionExpansion::Word };
case VK_RIGHT:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Right, SelectionExpansion::Word };
case VK_HOME:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Left, SelectionExpansion::Buffer };
case VK_END:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Right, SelectionExpansion::Buffer };
}
}
else
{
// Shift + _
switch (vkey)
{
case VK_HOME:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Left, SelectionExpansion::Viewport };
case VK_END:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Right, SelectionExpansion::Viewport };
case VK_PRIOR:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Up, SelectionExpansion::Viewport };
case VK_NEXT:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Down, SelectionExpansion::Viewport };
case VK_LEFT:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Left, SelectionExpansion::Char };
case VK_RIGHT:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Right, SelectionExpansion::Char };
case VK_UP:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Up, SelectionExpansion::Char };
case VK_DOWN:
return UpdateSelectionParams{ std::in_place, SelectionDirection::Down, SelectionExpansion::Char };
}
}
}
return std::nullopt;
}
void Terminal::UpdateSelection(SelectionDirection direction, SelectionExpansion mode)
{
// 1. Figure out which endpoint to update
// One of the endpoints is the pivot, signifying that the other endpoint is the one we want to move.
const bool movingEnd{ _selection->start == _selection->pivot };
auto targetPos{ movingEnd ? _selection->end : _selection->start };
// 2. Perform the movement
switch (mode)
{
case SelectionExpansion::Char:
_MoveByChar(direction, targetPos);
break;
case SelectionExpansion::Word:
_MoveByWord(direction, targetPos);
break;
case SelectionExpansion::Viewport:
_MoveByViewport(direction, targetPos);
break;
case SelectionExpansion::Buffer:
_MoveByBuffer(direction, targetPos);
break;
}
// 3. Actually modify the selection
// NOTE: targetStart doesn't matter here
bool targetStart = false;
std::tie(_selection->start, _selection->end) = _PivotSelection(targetPos, targetStart);
// 4. Scroll (if necessary)
if (const auto viewport = _GetVisibleViewport(); !viewport.IsInBounds(targetPos))
{
if (const auto amtAboveView = viewport.Top() - targetPos.Y; amtAboveView > 0)
{
// anchor is above visible viewport, scroll by that amount
_scrollOffset += amtAboveView;
}
else
{
// anchor is below visible viewport, scroll by that amount
const auto amtBelowView = targetPos.Y - viewport.BottomInclusive();
_scrollOffset -= amtBelowView;
}
_NotifyScrollEvent();
_buffer->GetRenderTarget().TriggerScroll();
}
}
void Terminal::_MoveByChar(SelectionDirection direction, COORD& pos)
{
switch (direction)
{
case SelectionDirection::Left:
_buffer->GetSize().DecrementInBounds(pos);
pos = _buffer->GetGlyphStart(pos);
break;
case SelectionDirection::Right:
_buffer->GetSize().IncrementInBounds(pos);
pos = _buffer->GetGlyphEnd(pos);
break;
case SelectionDirection::Up:
{
const auto bufferSize{ _buffer->GetSize() };
pos = { pos.X, std::clamp(base::ClampSub<short, short>(pos.Y, 1).RawValue(), bufferSize.Top(), bufferSize.BottomInclusive()) };
break;
}
case SelectionDirection::Down:
{
const auto bufferSize{ _buffer->GetSize() };
pos = { pos.X, std::clamp(base::ClampAdd<short, short>(pos.Y, 1).RawValue(), bufferSize.Top(), bufferSize.BottomInclusive()) };
break;
}
}
}
void Terminal::_MoveByWord(SelectionDirection direction, COORD& pos)
{
switch (direction)
{
case SelectionDirection::Left:
const auto wordStartPos{ _buffer->GetWordStart(pos, _wordDelimiters) };
if (_buffer->GetSize().CompareInBounds(_selection->pivot, pos) < 0)
{
// If we're moving towards the pivot, move one more cell
pos = wordStartPos;
_buffer->GetSize().DecrementInBounds(pos);
}
else if (wordStartPos == pos)
{
// already at the beginning of the current word,
// move to the beginning of the previous word
_buffer->GetSize().DecrementInBounds(pos);
pos = _buffer->GetWordStart(pos, _wordDelimiters);
}
else
{
// move to the beginning of the current word
pos = wordStartPos;
}
break;
case SelectionDirection::Right:
const auto wordEndPos{ _buffer->GetWordEnd(pos, _wordDelimiters) };
if (_buffer->GetSize().CompareInBounds(pos, _selection->pivot) < 0)
{
// If we're moving towards the pivot, move one more cell
pos = _buffer->GetWordEnd(pos, _wordDelimiters);
_buffer->GetSize().IncrementInBounds(pos);
}
else if (wordEndPos == pos)
{
// already at the end of the current word,
// move to the end of the next word
_buffer->GetSize().IncrementInBounds(pos);
pos = _buffer->GetWordEnd(pos, _wordDelimiters);
}
else
{
// move to the end of the current word
pos = wordEndPos;
}
break;
case SelectionDirection::Up:
_MoveByChar(direction, pos);
pos = _buffer->GetWordStart(pos, _wordDelimiters);
break;
case SelectionDirection::Down:
_MoveByChar(direction, pos);
pos = _buffer->GetWordEnd(pos, _wordDelimiters);
break;
}
}
void Terminal::_MoveByViewport(SelectionDirection direction, COORD& pos)
{
const auto bufferSize{ _buffer->GetSize() };
switch (direction)
{
case SelectionDirection::Left:
pos = { bufferSize.Left(), pos.Y };
break;
case SelectionDirection::Right:
pos = { bufferSize.RightInclusive(), pos.Y };
break;
case SelectionDirection::Up:
{
const auto viewportHeight{ _mutableViewport.Height() };
const auto newY{ base::ClampSub<short, short>(pos.Y, viewportHeight) };
pos = newY < bufferSize.Top() ? bufferSize.Origin() : COORD{ pos.X, newY };
break;
}
case SelectionDirection::Down:
{
const auto viewportHeight{ _mutableViewport.Height() };
const auto mutableBottom{ _mutableViewport.BottomInclusive() };
const auto newY{ base::ClampAdd<short, short>(pos.Y, viewportHeight) };
pos = newY > mutableBottom ? COORD{ bufferSize.RightInclusive(), mutableBottom } : COORD{ pos.X, newY };
break;
}
}
}
void Terminal::_MoveByBuffer(SelectionDirection direction, COORD& pos)
{
const auto bufferSize{ _buffer->GetSize() };
switch (direction)
{
case SelectionDirection::Left:
case SelectionDirection::Up:
pos = bufferSize.Origin();
break;
case SelectionDirection::Right:
case SelectionDirection::Down:
pos = { bufferSize.RightInclusive(), _mutableViewport.BottomInclusive() };
break;
}
}
// Method Description:
// - clear selection data and disable rendering it
#pragma warning(disable : 26440) // changing this to noexcept would require a change to ConHost's selection model

View File

@ -206,7 +206,7 @@ void Terminal::SelectNewRegion(const COORD coordStart, const COORD coordEnd)
realCoordEnd.Y -= gsl::narrow<short>(_VisibleStartIndex());
SetSelectionAnchor(realCoordStart);
SetSelectionEnd(realCoordEnd, SelectionExpansionMode::Cell);
SetSelectionEnd(realCoordEnd, SelectionExpansion::Char);
}
const std::wstring_view Terminal::GetConsoleTitle() const noexcept
@ -233,6 +233,9 @@ catch (...)
void Terminal::LockConsole() noexcept
{
_readWriteLock.lock();
#ifndef NDEBUG
_lastLocker = GetCurrentThreadId();
#endif
}
// Method Description:
@ -250,3 +253,12 @@ bool Terminal::IsScreenReversed() const noexcept
{
return _screenReversed;
}
const bool Terminal::IsUiaDataInitialized() const noexcept
{
// GH#11135: Windows Terminal needs to create and return an automation peer
// when a screen reader requests it. However, the terminal might not be fully
// initialized yet. So we use this to check if any crucial components of
// UiaData are not yet initialized.
return !!_buffer;
}

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
@ -7,6 +7,7 @@
#include "EnumEntry.h"
#include <LibraryResources.h>
#include "..\WinRTUtils\inc\Utils.h"
using namespace winrt::Windows::UI::Text;
using namespace winrt::Windows::UI::Xaml;

View File

@ -72,13 +72,15 @@
two font lists causes a crash within the ComboBox code.
As a workaround, introduce two ComboBox controls and only display one at a time.
-->
<ComboBox ItemTemplate="{StaticResource FontFaceComboBoxItemTemplate}"
<ComboBox x:Uid="Profile_FontFaceBox"
ItemTemplate="{StaticResource FontFaceComboBoxItemTemplate}"
ItemsSource="{x:Bind SourceProfile.MonospaceFontList, Mode=OneWay}"
SelectedItem="{x:Bind CurrentFontFace, Mode=OneWay}"
SelectionChanged="FontFace_SelectionChanged"
Style="{StaticResource ComboBoxSettingStyle}"
Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(ShowAllFonts), Mode=OneWay}" />
<ComboBox ItemTemplate="{StaticResource FontFaceComboBoxItemTemplate}"
<ComboBox x:Uid="Profile_FontFaceBox"
ItemTemplate="{StaticResource FontFaceComboBoxItemTemplate}"
ItemsSource="{x:Bind SourceProfile.CompleteFontList, Mode=OneWay}"
SelectedItem="{x:Bind CurrentFontFace, Mode=OneWay}"
SelectionChanged="FontFace_SelectionChanged"
@ -97,7 +99,8 @@
HasSettingValue="{x:Bind Appearance.HasFontSize, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.FontSizeOverrideSource, Mode=OneWay}"
Visibility="{x:Bind Appearance.IsDefault, Mode=OneWay}">
<muxc:NumberBox AcceptsExpression="False"
<muxc:NumberBox x:Uid="Profile_FontSizeBox"
AcceptsExpression="False"
LargeChange="10"
Maximum="128"
Minimum="1"

View File

@ -166,12 +166,14 @@
<!-- Columns -->
<local:SettingContainer x:Uid="Globals_InitialCols"
Margin="0">
<muxc:NumberBox Style="{StaticResource LaunchSizeNumberBoxStyle}"
<muxc:NumberBox x:Uid="Globals_InitialColsBox"
Style="{StaticResource LaunchSizeNumberBoxStyle}"
Value="{x:Bind State.Settings.GlobalSettings.InitialCols, Mode=TwoWay}" />
</local:SettingContainer>
<!-- Rows -->
<local:SettingContainer x:Uid="Globals_InitialRows">
<muxc:NumberBox Style="{StaticResource LaunchSizeNumberBoxStyle}"
<muxc:NumberBox x:Uid="Globals_InitialRowsBox"
Style="{StaticResource LaunchSizeNumberBoxStyle}"
Value="{x:Bind State.Settings.GlobalSettings.InitialRows, Mode=TwoWay}" />
</local:SettingContainer>
</StackPanel>

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
@ -8,6 +8,7 @@
#include "EnumEntry.h"
#include <LibraryResources.h>
#include "..\WinRTUtils\inc\Utils.h"
using namespace winrt::Windows::UI::Text;
using namespace winrt::Windows::UI::Xaml;

View File

@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
@ -22,7 +22,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void SetAcrylicOpacityPercentageValue(double value)
{
_profile.DefaultAppearance().Opacity(winrt::Microsoft::Terminal::Settings::Editor::Converters::PercentageValueToPercentage(value));
Opacity(winrt::Microsoft::Terminal::Settings::Editor::Converters::PercentageValueToPercentage(value));
};
void SetPadding(double value)

View File

@ -81,7 +81,8 @@
SettingOverrideSource="{x:Bind State.Profile.CommandlineOverrideSource, Mode=OneWay}"
Visibility="{x:Bind local:Converters.InvertedBooleanToVisibility(State.Profile.IsBaseLayer), Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<TextBox IsSpellCheckEnabled="False"
<TextBox x:Uid="Profile_CommandlineBox"
IsSpellCheckEnabled="False"
Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind State.Profile.Commandline, Mode=TwoWay}" />
<Button x:Uid="Profile_CommandlineBrowse"
@ -99,7 +100,8 @@
SettingOverrideSource="{x:Bind State.Profile.StartingDirectoryOverrideSource, Mode=OneWay}">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal">
<TextBox IsEnabled="{x:Bind State.Profile.UseCustomStartingDirectory, Mode=OneWay}"
<TextBox x:Uid="Profile_StartingDirectoryBox"
IsEnabled="{x:Bind State.Profile.UseCustomStartingDirectory, Mode=OneWay}"
IsSpellCheckEnabled="False"
Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind State.Profile.StartingDirectory, Mode=TwoWay}" />
@ -121,7 +123,8 @@
HasSettingValue="{x:Bind State.Profile.HasIcon, Mode=OneWay}"
SettingOverrideSource="{x:Bind State.Profile.IconOverrideSource, Mode=OneWay}">
<StackPanel Orientation="Horizontal">
<TextBox FontFamily="Segoe UI, Segoe MDL2 Assets"
<TextBox x:Uid="Profile_IconBox"
FontFamily="Segoe UI, Segoe MDL2 Assets"
IsSpellCheckEnabled="False"
Style="{StaticResource TextBoxSettingStyle}"
Text="{x:Bind State.Profile.Icon, Mode=TwoWay}" />
@ -246,6 +249,7 @@
<!-- Opacity -->
<local:SettingContainer x:Name="OpacityContainer"
x:Uid="Profile_Opacity"
Margin="0"
ClearSettingValue="{x:Bind State.Profile.ClearOpacity}"
HasSettingValue="{x:Bind State.Profile.HasOpacity, Mode=OneWay}"
SettingOverrideSource="{x:Bind State.Profile.OpacityOverrideSource, Mode=OneWay}">
@ -438,7 +442,8 @@
ClearSettingValue="{x:Bind State.Profile.ClearHistorySize}"
HasSettingValue="{x:Bind State.Profile.HasHistorySize, Mode=OneWay}"
SettingOverrideSource="{x:Bind State.Profile.HistorySizeOverrideSource, Mode=OneWay}">
<muxc:NumberBox LargeChange="100"
<muxc:NumberBox x:Uid="Profile_HistorySizeBox"
LargeChange="100"
Minimum="0"
SmallChange="10"
Style="{StaticResource NumberBoxSettingStyle}"

View File

@ -291,6 +291,14 @@
<value>The number of rows displayed in the window upon first load. Measured in characters.</value>
<comment>A description for what the "rows" setting does. Presented near "Globals_InitialRows.Header".</comment>
</data>
<data name="Globals_InitialColsBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Initial Columns</value>
<comment>Name for a control to choose the number of columns in the terminal's text grid.</comment>
</data>
<data name="Globals_InitialRowsBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Initial Rows</value>
<comment>Name for a control to choose the number of rows in the terminal's text grid.</comment>
</data>
<data name="Globals_FirstWindowPreference.Header" xml:space="preserve">
<value>When Terminal starts</value>
<comment>Header for a control to select how the terminal should load its first window.</comment>
@ -304,8 +312,8 @@
<comment>An option to choose from for the "First window preference" setting. Open the default profile.</comment>
</data>
<data name="Globals_FirstWindowPreferencePersistedWindowLayout.Content" xml:space="preserve">
<value>Open tabs from a previous session</value>
<comment>An option to choose from for the "First window preference" setting. Reopen the layout from the last session.</comment>
<value>Open windows from a previous session</value>
<comment>An option to choose from for the "First window preference" setting. Reopen the layouts from the last session.</comment>
</data>
<data name="Globals_LaunchMode.Header" xml:space="preserve">
<value>Launch mode</value>
@ -496,7 +504,7 @@
<comment>A description for what the "acrylic opacity" setting does. Presented near "Profile_AcrylicOpacity.Header".</comment>
</data>
<data name="Profile_Opacity.Header" xml:space="preserve">
<value>Background Opacity</value>
<value>Background opacity</value>
<comment>Header for a control to determine the level of opacity for the background of the control. The user can choose to make the background of the app more or less opaque.</comment>
</data>
<data name="Profile_Opacity.HelpText" xml:space="preserve">
@ -655,6 +663,10 @@
<value>Command line</value>
<comment>Header for a control to determine commandline executable (i.e. a .exe file) to run when a terminal session of this profile is launched.</comment>
</data>
<data name="Profile_CommandlineBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Command line</value>
<comment>Name for a control to determine commandline executable (i.e. a .exe file) to run when a terminal session of this profile is launched.</comment>
</data>
<data name="Profile_Commandline.HelpText" xml:space="preserve">
<value>Executable used in the profile.</value>
<comment>A description for what the "command line" setting does. Presented near "Profile_Commandline".</comment>
@ -703,6 +715,10 @@
<value>Font face</value>
<comment>Header for a control to select the font for text in the app.</comment>
</data>
<data name="Profile_FontFaceBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Font face</value>
<comment>Name for a control to select the font for text in the app.</comment>
</data>
<data name="Profile_FontFace.HelpText" xml:space="preserve">
<value>Name of the font face used in the profile.</value>
<comment>A description for what the "font face" setting does. Presented near "Profile_FontFace".</comment>
@ -711,6 +727,10 @@
<value>Font size</value>
<comment>Header for a control to determine the size of the text in the app.</comment>
</data>
<data name="Profile_FontSizeBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Font size</value>
<comment>Name for a control to determine the size of the text in the app.</comment>
</data>
<data name="Profile_FontSize.HelpText" xml:space="preserve">
<value>Size of the font in points.</value>
<comment>A description for what the "font size" setting does. Presented near "Profile_FontSize".</comment>
@ -739,6 +759,10 @@
<value>History size</value>
<comment>Header for a control to determine how many lines of text can be saved in a session. In terminals, the "history" is the output generated within your session.</comment>
</data>
<data name="Profile_HistorySizeBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>History size</value>
<comment>Name for a control to determine how many lines of text can be saved in a session. In terminals, the "history" is the output generated within your session.</comment>
</data>
<data name="Profile_HistorySize.HelpText" xml:space="preserve">
<value>The number of lines above the ones displayed in the window you can scroll back to.</value>
<comment>A description for what the "history size" setting does. Presented near "Profile_HistorySize".</comment>
@ -747,6 +771,10 @@
<value>Icon</value>
<comment>Header for a control to determine what icon can be used to represent this profile. This is not necessarily a file path, but can be one.</comment>
</data>
<data name="Profile_IconBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Icon</value>
<comment>Name for a control to determine what icon can be used to represent this profile. This is not necessarily a file path, but can be one.</comment>
</data>
<data name="Profile_Icon.HelpText" xml:space="preserve">
<value>Emoji or image file location of the icon used in the profile.</value>
<comment>A description for what the "icon" setting does. Presented near "Profile_Icon".</comment>
@ -791,6 +819,10 @@
<value>Starting directory</value>
<comment>Header for a control to determine the directory the session opens it at launch. This is on a text box that accepts folder paths.</comment>
</data>
<data name="Profile_StartingDirectoryBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Starting directory</value>
<comment>Name for a control to determine the directory the session opens it at launch. This is on a text box that accepts folder paths.</comment>
</data>
<data name="Profile_StartingDirectory.HelpText" xml:space="preserve">
<value>The directory the profile starts in when it is loaded.</value>
<comment>A description for what the "starting directory" setting does. Presented near "Profile_StartingDirectory".</comment>
@ -1227,7 +1259,7 @@
<comment>Header for a control to how text is formatted</comment>
</data>
<data name="Appearance_IntenseTextStyle.Header" xml:space="preserve">
<value>Intense Text Style</value>
<value>Intense text style</value>
<comment>Header for a control to select how "intense" text is formatted (bold, bright, both or none)</comment>
</data>
<data name="Appearance_IntenseTextStyleNone.Content" xml:space="preserve">

View File

@ -15,30 +15,6 @@ using namespace winrt::Windows::UI::Xaml;
UTILS_DEFINE_LIBRARY_RESOURCE_SCOPE(L"Microsoft.Terminal.Settings.Editor/Resources");
// Function Description:
// - Helper that opens a file picker pre-seeded with image file types.
winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> OpenImagePicker(HWND parentHwnd)
{
static constexpr COMDLG_FILTERSPEC supportedImageFileTypes[] = {
{ L"All Supported Bitmap Types (*.jpg, *.jpeg, *.png, *.bmp, *.gif, *.tiff, *.ico)", L"*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.tiff;*.ico" },
{ L"All Files (*.*)", L"*.*" }
};
static constexpr winrt::guid clientGuidImagePicker{ 0x55675F54, 0x74A1, 0x4552, { 0xA3, 0x9D, 0x94, 0xAE, 0x85, 0xD8, 0xF2, 0x7A } };
return OpenFilePicker(parentHwnd, [](auto&& dialog) {
THROW_IF_FAILED(dialog->SetClientGuid(clientGuidImagePicker));
try
{
auto pictureFolderShellItem{ winrt::capture<IShellItem>(&SHGetKnownFolderItem, FOLDERID_PicturesLibrary, KF_FLAG_DEFAULT, nullptr) };
dialog->SetDefaultFolder(pictureFolderShellItem.get());
}
CATCH_LOG(); // non-fatal
THROW_IF_FAILED(dialog->SetFileTypes(ARRAYSIZE(supportedImageFileTypes), supportedImageFileTypes));
THROW_IF_FAILED(dialog->SetFileTypeIndex(1)); // the array is 1-indexed
THROW_IF_FAILED(dialog->SetDefaultExtension(L"jpg;jpeg;png;bmp;gif;tiff;ico"));
});
}
namespace winrt::Microsoft::Terminal::Settings
{
hstring GetSelectedItemTag(winrt::Windows::Foundation::IInspectable const& comboBoxAsInspectable)

View File

@ -97,42 +97,6 @@ public: \
private: \
static winrt::Windows::UI::Xaml::DependencyProperty _##name##Property;
// Function Description:
// - This function presents a File Open "common dialog" and returns its selected file asynchronously.
// Parameters:
// - customize: A lambda that receives an IFileDialog* to customize.
// Return value:
// (async) path to the selected item.
template<typename TLambda>
winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> OpenFilePicker(HWND parentHwnd, TLambda&& customize)
{
auto fileDialog{ winrt::create_instance<IFileDialog>(CLSID_FileOpenDialog) };
DWORD flags{};
THROW_IF_FAILED(fileDialog->GetOptions(&flags));
THROW_IF_FAILED(fileDialog->SetOptions(flags | FOS_FORCEFILESYSTEM | FOS_NOCHANGEDIR | FOS_DONTADDTORECENT)); // filesystem objects only; no recent places
customize(fileDialog.get());
auto hr{ fileDialog->Show(parentHwnd) };
if (!SUCCEEDED(hr))
{
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))
{
co_return winrt::hstring{};
}
THROW_HR(hr);
}
winrt::com_ptr<IShellItem> result;
THROW_IF_FAILED(fileDialog->GetResult(result.put()));
wil::unique_cotaskmem_string filePath;
THROW_IF_FAILED(result->GetDisplayName(SIGDN_FILESYSPATH, &filePath));
co_return winrt::hstring{ filePath.get() };
}
winrt::Windows::Foundation::IAsyncOperation<winrt::hstring> OpenImagePicker(HWND parentHwnd);
namespace winrt::Microsoft::Terminal::Settings
{
winrt::hstring GetSelectedItemTag(winrt::Windows::Foundation::IInspectable const& comboBoxAsInspectable);

View File

@ -38,6 +38,7 @@
#include "MultipleActionsArgs.g.cpp"
#include <LibraryResources.h>
#include <WtExeUtils.h>
using namespace winrt::Microsoft::Terminal::Control;
@ -121,15 +122,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
if (!StartingDirectory().empty())
{
// If the directory ends in a '\', we need to add another one on so that the enclosing quote added
// afterwards isn't escaped
const auto trailingBackslashEscape = StartingDirectory().back() == L'\\' ? L"\\" : L"";
ss << fmt::format(L"--startingDirectory \"{}{}\" ", StartingDirectory(), trailingBackslashEscape);
ss << fmt::format(L"--startingDirectory {} ", QuoteAndEscapeCommandlineArg(StartingDirectory()));
}
if (!TabTitle().empty())
{
ss << fmt::format(L"--title \"{}\" ", TabTitle());
ss << fmt::format(L"--title {} ", QuoteAndEscapeCommandlineArg(TabTitle()));
}
if (TabColor())
@ -152,7 +150,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
if (!ColorScheme().empty())
{
ss << fmt::format(L"--colorScheme \"{}\" ", ColorScheme());
ss << fmt::format(L"--colorScheme {} ", QuoteAndEscapeCommandlineArg(ColorScheme()));
}
if (!Commandline().empty())
@ -300,6 +298,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
return RS_(L"MoveFocusPreviousInOrder");
case FocusDirection::First:
return RS_(L"MoveFocusFirstPane");
case FocusDirection::Parent:
return RS_(L"MoveFocusParentPane");
case FocusDirection::Child:
return RS_(L"MoveFocusChildPane");
}
return winrt::hstring{

View File

@ -864,6 +864,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
struct SetTabColorArgs : public SetTabColorArgsT<SetTabColorArgs>
{
SetTabColorArgs() = default;
SetTabColorArgs(Windows::UI::Color tabColor) :
_TabColor{ tabColor } {}
ACTION_ARG(Windows::Foundation::IReference<Windows::UI::Color>, TabColor, nullptr);
static constexpr std::string_view ColorKey{ "color" };
@ -1582,6 +1584,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
struct RenameWindowArgs : public RenameWindowArgsT<RenameWindowArgs>
{
RenameWindowArgs() = default;
RenameWindowArgs(winrt::hstring name) :
_Name{ name } {};
ACTION_ARG(winrt::hstring, Name);
static constexpr std::string_view NameKey{ "name" };
@ -1679,7 +1683,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
JsonUtils::SetValueForKey(json, NameKey, args->_Name);
JsonUtils::SetValueForKey(json, DesktopKey, args->_Desktop);
JsonUtils::SetValueForKey(json, MonitorKey, args->_Monitor);
JsonUtils::GetValueForKey(json, DropdownDurationKey, args->_DropdownDuration);
JsonUtils::SetValueForKey(json, DropdownDurationKey, args->_DropdownDuration);
JsonUtils::SetValueForKey(json, ToggleVisibilityKey, args->_ToggleVisibility);
return json;
}
@ -1869,9 +1873,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation
BASIC_FACTORY(NewTabArgs);
BASIC_FACTORY(MoveFocusArgs);
BASIC_FACTORY(MovePaneArgs);
BASIC_FACTORY(SetTabColorArgs);
BASIC_FACTORY(SwapPaneArgs);
BASIC_FACTORY(SplitPaneArgs);
BASIC_FACTORY(SetColorSchemeArgs);
BASIC_FACTORY(RenameWindowArgs);
BASIC_FACTORY(ExecuteCommandlineArgs);
BASIC_FACTORY(CloseOtherTabsArgs);
BASIC_FACTORY(CloseTabsAfterArgs);

View File

@ -38,7 +38,9 @@ namespace Microsoft.Terminal.Settings.Model
Previous,
PreviousInOrder,
NextInOrder,
First
First,
Parent,
Child
};
enum SplitDirection
@ -213,6 +215,7 @@ namespace Microsoft.Terminal.Settings.Model
[default_interface] runtimeclass SetTabColorArgs : IActionArgs
{
SetTabColorArgs(Windows.UI.Color tabColor);
Windows.Foundation.IReference<Windows.UI.Color> TabColor { get; };
};
@ -294,6 +297,7 @@ namespace Microsoft.Terminal.Settings.Model
[default_interface] runtimeclass RenameWindowArgs : IActionArgs
{
RenameWindowArgs(String name);
String Name { get; };
};

View File

@ -60,6 +60,31 @@ using namespace ::Microsoft::Terminal::Settings::Model;
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
winrt::hstring WindowLayout::ToJson(const Model::WindowLayout& layout)
{
JsonUtils::ConversionTrait<Model::WindowLayout> trait;
auto json = trait.ToJson(layout);
Json::StreamWriterBuilder wbuilder;
const auto content = Json::writeString(wbuilder, json);
return hstring{ til::u8u16(content) };
}
Model::WindowLayout WindowLayout::FromJson(const hstring& str)
{
auto data = til::u16u8(str);
std::string errs;
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
Json::Value root;
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
{
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
}
JsonUtils::ConversionTrait<Model::WindowLayout> trait;
return trait.FromJson(root);
}
// Returns the application-global ApplicationState object.
Microsoft::Terminal::Settings::Model::ApplicationState ApplicationState::SharedInstance()
{
@ -108,6 +133,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{ \
auto state = _state.lock(); \
state->name.emplace(value); \
state->name##Changed = true; \
} \
\
_throttler(); \
@ -115,34 +141,50 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
#undef MTSM_APPLICATION_STATE_GEN
Json::Value ApplicationState::_getRoot(const locked_hfile& file) const noexcept
{
Json::Value root;
try
{
const auto data = ReadUTF8FileLocked(file);
if (data.empty())
{
return root;
}
std::string errs;
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
{
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
}
}
CATCH_LOG()
return root;
}
// Deserializes the state.json at _path into this ApplicationState.
// * ANY errors during app state will result in the creation of a new empty state.
// * ANY errors during runtime will result in changes being partially ignored.
void ApplicationState::_read() const noexcept
try
{
const auto data = ReadUTF8FileIfExists(_path).value_or(std::string{});
if (data.empty())
{
return;
}
std::string errs;
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder::CharReaderBuilder().newCharReader() };
Json::Value root;
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
{
throw winrt::hresult_error(WEB_E_INVALID_JSON_STRING, winrt::to_hstring(errs));
}
auto state = _state.lock();
const auto file = OpenFileReadSharedLocked(_path);
auto root = _getRoot(file);
// GetValueForKey() comes in two variants:
// * take a std::optional<T> reference
// * return std::optional<T> by value
// At the time of writing the former version skips missing fields in the json,
// but we want to explicitly clear state fields that were removed from state.json.
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key);
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
if (!state->name##Changed) \
{ \
state->name = JsonUtils::GetValueForKey<std::optional<type>>(root, key); \
}
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
#undef MTSM_APPLICATION_STATE_GEN
}
@ -152,21 +194,29 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
// * Errors are only logged.
// * _state->_writeScheduled is set to false, signaling our
// setters that _synchronize() needs to be called again.
void ApplicationState::_write() const noexcept
void ApplicationState::_write() noexcept
try
{
Json::Value root{ Json::objectValue };
// re-read the state so that we can only update the properties that were changed.
Json::Value root{};
{
auto state = _state.lock_shared();
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) JsonUtils::SetValueForKey(root, key, state->name);
auto state = _state.lock();
const auto file = OpenFileRWExclusiveLocked(_path);
root = _getRoot(file);
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
if (state->name##Changed) \
{ \
JsonUtils::SetValueForKey(root, key, state->name); \
state->name##Changed = false; \
}
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
#undef MTSM_APPLICATION_STATE_GEN
}
Json::StreamWriterBuilder wbuilder;
const auto content = Json::writeString(wbuilder, root);
WriteUTF8FileAtomic(_path, content);
Json::StreamWriterBuilder wbuilder;
const auto content = Json::writeString(wbuilder, root);
WriteUTF8FileLocked(file, content);
}
}
CATCH_LOG()
}

View File

@ -18,6 +18,7 @@ Abstract:
#include <inc/cppwinrt_utils.h>
#include <til/mutex.h>
#include <til/throttled_func.h>
#include "FileUtils.h"
#include <JsonUtils.h>
// This macro generates all getters and setters for ApplicationState.
@ -33,6 +34,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
struct WindowLayout : WindowLayoutT<WindowLayout>
{
static winrt::hstring ToJson(const Model::WindowLayout& layout);
static Model::WindowLayout FromJson(const winrt::hstring& json);
WINRT_PROPERTY(Windows::Foundation::Collections::IVector<Model::ActionAndArgs>, TabLayout, nullptr);
WINRT_PROPERTY(winrt::Windows::Foundation::IReference<Model::LaunchPosition>, InitialPosition, nullptr);
WINRT_PROPERTY(winrt::Windows::Foundation::IReference<winrt::Windows::Foundation::Size>, InitialSize, nullptr);
@ -63,12 +67,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
private:
struct state_t
{
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) std::optional<type> name{ __VA_ARGS__ };
#define MTSM_APPLICATION_STATE_GEN(type, name, key, ...) \
std::optional<type> name{ __VA_ARGS__ }; \
bool name##Changed = false;
MTSM_APPLICATION_STATE_FIELDS(MTSM_APPLICATION_STATE_GEN)
#undef MTSM_APPLICATION_STATE_GEN
};
void _write() const noexcept;
Json::Value _getRoot(const winrt::Microsoft::Terminal::Settings::Model::locked_hfile& file) const noexcept;
void _write() noexcept;
void _read() const noexcept;
std::filesystem::path _path;

View File

@ -15,6 +15,9 @@ namespace Microsoft.Terminal.Settings.Model
{
WindowLayout();
static String ToJson(WindowLayout layout);
static WindowLayout FromJson(String json);
Windows.Foundation.Collections.IVector<ActionAndArgs> TabLayout;
Windows.Foundation.IReference<LaunchPosition> InitialPosition;
Windows.Foundation.IReference<Windows.Foundation.Size> InitialSize;

View File

@ -1,36 +0,0 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "BaseVisualStudioGenerator.h"
#include "DynamicProfileUtils.h"
using namespace winrt::Microsoft::Terminal::Settings::Model;
void BaseVisualStudioGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
{
// There's no point in enumerating valid Visual Studio instances more than once,
// so cache them for use by both Visual Studio profile generators.
static const auto instances = VsSetupConfiguration::QueryInstances();
for (auto const& instance : instances)
{
try
{
if (!IsInstanceValid(instance))
{
continue;
}
const auto seed = GetProfileGuidSeed(instance);
const winrt::guid profileGuid{ ::Microsoft::Console::Utils::CreateV5Uuid(TERMINAL_PROFILE_NAMESPACE_GUID, gsl::as_bytes(gsl::make_span(seed))) };
auto profile = winrt::make_self<implementation::Profile>(profileGuid);
profile->Name(winrt::hstring{ GetProfileName(instance) });
profile->Commandline(winrt::hstring{ GetProfileCommandLine(instance) });
profile->StartingDirectory(winrt::hstring{ instance.GetInstallationPath() });
profile->Icon(winrt::hstring{ GetProfileIconPath() });
profiles.emplace_back(std::move(profile));
}
CATCH_LOG();
}
}

View File

@ -1,35 +0,0 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- BaseVisualStudioGenerator
Abstract:
- Base generator for Visual Studio Developer shell profiles
Author(s):
- Charles Willis - October 2020
--*/
#pragma once
#include "IDynamicProfileGenerator.h"
#include "VsSetupConfiguration.h"
namespace winrt::Microsoft::Terminal::Settings::Model
{
class BaseVisualStudioGenerator : public IDynamicProfileGenerator
{
public:
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
protected:
virtual bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance& instance) const = 0;
virtual std::wstring GetProfileName(const VsSetupConfiguration::VsSetupInstance& instance) const = 0;
virtual std::wstring GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance& instance) const = 0;
virtual std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance& instance) const = 0;
virtual std::wstring GetProfileIconPath() const = 0;
};
};

View File

@ -5,6 +5,8 @@
#include "CascadiaSettings.h"
#include "CascadiaSettings.g.cpp"
#include "FileUtils.h"
#include <LibraryResources.h>
#include <VersionHelpers.h>
@ -553,9 +555,11 @@ Model::Profile CascadiaSettings::GetProfileForArgs(const Model::NewTerminalArgs&
FindProfile(GlobalSettings().DefaultProfile()) :
ProfileDefaults();
}
// For compatibility with the stable version's behavior, return the default by GUID in all other cases.
return FindProfile(GlobalSettings().DefaultProfile());
else
{
// For compatibility with the stable version's behavior, return the default by GUID in all other cases.
return FindProfile(GlobalSettings().DefaultProfile());
}
}
// Method Description:
@ -878,3 +882,12 @@ void CascadiaSettings::CurrentDefaultTerminal(const Model::DefaultTerminal& term
{
_currentDefaultTerminal = terminal;
}
void CascadiaSettings::ExportFile(winrt::hstring path, winrt::hstring content)
{
try
{
winrt::Microsoft::Terminal::Settings::Model::WriteUTF8FileAtomic({ path.c_str() }, til::u16u8(content));
}
CATCH_LOG();
}

View File

@ -44,6 +44,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
winrt::com_ptr<implementation::Profile> baseLayerProfile;
std::vector<winrt::com_ptr<implementation::Profile>> profiles;
std::unordered_map<winrt::guid, winrt::com_ptr<implementation::Profile>> profilesByGuid;
void clear();
};
struct SettingsLoader
@ -63,14 +65,25 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
bool duplicateProfile = false;
private:
struct JsonSettings
{
Json::Value root;
const Json::Value& colorSchemes;
const Json::Value& profileDefaults;
const Json::Value& profilesList;
};
static std::pair<size_t, size_t> _lineAndColumnFromPosition(const std::string_view& string, const size_t position);
static void _rethrowSerializationExceptionWithLocationInfo(const JsonUtils::DeserializationError& e, const std::string_view& settingsString);
static Json::Value _parseJSON(const std::string_view& content);
static const Json::Value& _getJSONValue(const Json::Value& json, const std::string_view& key) noexcept;
static bool _isValidProfileObject(const Json::Value& profileJson);
gsl::span<const winrt::com_ptr<implementation::Profile>> _getNonUserOriginProfiles() const;
void _parse(const OriginTag origin, const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings);
void _parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings);
static JsonSettings _parseJson(const std::string_view& content);
static winrt::com_ptr<implementation::Profile> _parseProfile(const OriginTag origin, const winrt::hstring& source, const Json::Value& profileJson);
void _appendProfile(winrt::com_ptr<implementation::Profile>&& profile, ParsedSettings& settings);
static void _addParentProfile(const winrt::com_ptr<implementation::Profile>& profile, ParsedSettings& settings);
void _executeGenerator(const IDynamicProfileGenerator& generator);
std::unordered_set<std::wstring_view> _ignoredNamespaces;
@ -89,6 +102,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static winrt::hstring DefaultSettingsPath();
static winrt::hstring ApplicationDisplayName();
static winrt::hstring ApplicationVersion();
static void ExportFile(winrt::hstring path, winrt::hstring content);
CascadiaSettings() noexcept = default;
CascadiaSettings(const winrt::hstring& userJSON, const winrt::hstring& inboxJSON);

View File

@ -18,6 +18,8 @@ namespace Microsoft.Terminal.Settings.Model
static String ApplicationDisplayName { get; };
static String ApplicationVersion { get; };
static void ExportFile(String path, String content);
CascadiaSettings(String userJSON, String inboxJSON);

View File

@ -11,8 +11,7 @@
#include "AzureCloudShellGenerator.h"
#include "PowershellCoreProfileGenerator.h"
#include "VsDevCmdGenerator.h"
#include "VsDevShellGenerator.h"
#include "VisualStudioGenerator.h"
#include "WslDistroGenerator.h"
// The following files are generated at build time into the "Generated Files" directory.
@ -81,6 +80,14 @@ static std::filesystem::path buildPath(const std::wstring_view& lhs, const std::
return { std::move(buffer) };
}
void ParsedSettings::clear()
{
globals = {};
baseLayerProfile = {};
profiles.clear();
profilesByGuid.clear();
}
// This is a convenience method used by the CascadiaSettings constructor.
// It runs some basic settings layering without relying on external programs or files.
// This makes it suitable for most unit tests.
@ -131,8 +138,7 @@ void SettingsLoader::GenerateProfiles()
_executeGenerator(PowershellCoreProfileGenerator{});
_executeGenerator(WslDistroGenerator{});
_executeGenerator(AzureCloudShellGenerator{});
_executeGenerator(VsDevCmdGenerator{});
_executeGenerator(VsDevShellGenerator{});
_executeGenerator(VisualStudioGenerator{});
}
// A new settings.json gets a special treatment:
@ -178,22 +184,7 @@ void SettingsLoader::MergeInboxIntoUserSettings()
{
for (const auto& profile : inboxSettings.profiles)
{
if (const auto [it, inserted] = userSettings.profilesByGuid.emplace(profile->Guid(), profile); !inserted)
{
// If inserted is false, we got a matching user profile with identical GUID.
// --> The generated profile is a parent of the existing user profile.
it->second->InsertParent(profile);
}
else
{
// If inserted is true, then this is a generated profile that doesn't exist in the user's settings.
// While emplace() has already created an appropriate entry in .profilesByGuid, we still need to
// add it to .profiles (which is basically a sorted list of .profilesByGuid's values).
//
// When a user modifies a profile they shouldn't modify the (static/constant)
// inbox profile of course. That's why we need to call CreateChild here.
userSettings.profiles.emplace_back(CreateChild(profile));
}
_addParentProfile(profile, userSettings);
}
}
@ -216,7 +207,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
try
{
const auto content = ReadUTF8File(fragmentExt.path());
_parse(OriginTag::Fragment, source, content, fragmentSettings);
_parseFragment(source, content, fragmentSettings);
for (const auto& fragmentProfile : fragmentSettings.profiles)
{
@ -229,7 +220,7 @@ void SettingsLoader::FindFragmentsAndMergeIntoUserSettings()
}
else
{
_appendProfile(CreateChild(fragmentProfile), userSettings);
_addParentProfile(fragmentProfile, userSettings);
}
}
@ -433,17 +424,6 @@ const Json::Value& SettingsLoader::_getJSONValue(const Json::Value& json, const
return Json::Value::nullSingleton();
}
// Returns true if the given Json::Value looks like a profile.
// We introduced a bug (GH#9962, fixed in GH#9964) that would result in one or
// more nameless, guid-less profiles being emitted into the user's settings file.
// Those profiles would show up in the list as "Default" later.
bool SettingsLoader::_isValidProfileObject(const Json::Value& profileJson)
{
return profileJson.isObject() &&
(profileJson.isMember(NameKey.data(), NameKey.data() + NameKey.size()) || // has a name (can generate a guid)
profileJson.isMember(GuidKey.data(), GuidKey.data() + GuidKey.size())); // or has a guid
}
// We treat userSettings.profiles as an append-only array and will
// append profiles into the userSettings as necessary in this function.
// _userProfileCount stores the number of profiles that were in userJSON during construction.
@ -458,81 +438,131 @@ gsl::span<const winrt::com_ptr<Profile>> SettingsLoader::_getNonUserOriginProfil
}
// Parses the given JSON string ("content") and fills a ParsedSettings instance with it.
// This function is to be used for user settings files.
void SettingsLoader::_parse(const OriginTag origin, const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings)
{
const auto json = content.empty() ? Json::Value{ Json::ValueType::objectValue } : _parseJSON(content);
const auto& profilesObject = _getJSONValue(json, ProfilesKey);
const auto& defaultsObject = _getJSONValue(profilesObject, DefaultSettingsKey);
const auto& profilesArray = profilesObject.isArray() ? profilesObject : _getJSONValue(profilesObject, ProfilesListKey);
const auto json = _parseJson(content);
settings.clear();
// globals
{
settings.globals = GlobalAppSettings::FromJson(json);
settings.globals = GlobalAppSettings::FromJson(json.root);
if (const auto& schemes = _getJSONValue(json, SchemesKey))
for (const auto& schemeJson : json.colorSchemes)
{
for (const auto& schemeJson : schemes)
if (const auto scheme = ColorScheme::FromJson(schemeJson))
{
if (schemeJson.isObject())
{
if (const auto scheme = ColorScheme::FromJson(schemeJson))
{
settings.globals->AddColorScheme(*scheme);
}
}
settings.globals->AddColorScheme(*scheme);
}
}
}
// profiles.defaults
{
settings.baseLayerProfile = Profile::FromJson(defaultsObject);
settings.baseLayerProfile = Profile::FromJson(json.profileDefaults);
// Remove the `guid` member from the default settings.
// That will hyper-explode, so just don't let them do that.
settings.baseLayerProfile->ClearGuid();
settings.baseLayerProfile->Origin(OriginTag::ProfilesDefaults);
}
// profiles.list
{
const auto size = profilesArray.size();
// NOTE: This function is supposed to *replace* the contents of ParsedSettings. Don't break this promise.
// SettingsLoader::FindFragmentsAndMergeIntoUserSettings relies on this.
settings.profiles.clear();
const auto size = json.profilesList.size();
settings.profiles.reserve(size);
settings.profilesByGuid.clear();
settings.profilesByGuid.reserve(size);
for (const auto& profileJson : profilesArray)
for (const auto& profileJson : json.profilesList)
{
if (_isValidProfileObject(profileJson))
auto profile = _parseProfile(origin, source, profileJson);
// GH#9962: Discard Guid-less, Name-less profiles.
if (profile->HasGuid())
{
auto profile = Profile::FromJson(profileJson);
profile->Origin(origin);
// The Guid() generation below depends on the value of Source().
// --> Provide one if we got one.
if (!source.empty())
{
profile->Source(source);
}
// The Guid() getter generates one from Name() and Source() if none exists otherwise.
// We want to ensure that every profile has a GUID no matter what, not just to
// cache the value, but also to make them consistently identifiable later on.
if (!profile->HasGuid())
{
profile->Guid(profile->Guid());
}
_appendProfile(std::move(profile), settings);
}
}
}
}
// Just like _parse, but is to be used for fragment files, which don't support anything but color
// schemes and profiles. Additionally this function supports profiles which specify an "updates" key.
void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::string_view& content, ParsedSettings& settings)
{
const auto json = _parseJson(content);
settings.clear();
{
settings.globals = winrt::make_self<GlobalAppSettings>();
for (const auto& schemeJson : json.colorSchemes)
{
try
{
if (const auto scheme = ColorScheme::FromJson(schemeJson))
{
settings.globals->AddColorScheme(*scheme);
}
}
CATCH_LOG()
}
}
{
const auto size = json.profilesList.size();
settings.profiles.reserve(size);
settings.profilesByGuid.reserve(size);
for (const auto& profileJson : json.profilesList)
{
try
{
auto profile = _parseProfile(OriginTag::Fragment, source, profileJson);
// GH#9962: Discard Guid-less, Name-less profiles, but...
// allow ones with an Updates field, as those are special for fragments.
if (profile->HasGuid() || profile->Updates() != winrt::guid{})
{
_appendProfile(std::move(profile), settings);
}
}
CATCH_LOG()
}
}
}
SettingsLoader::JsonSettings SettingsLoader::_parseJson(const std::string_view& content)
{
auto root = content.empty() ? Json::Value{ Json::ValueType::objectValue } : _parseJSON(content);
const auto& colorSchemes = _getJSONValue(root, SchemesKey);
const auto& profilesObject = _getJSONValue(root, ProfilesKey);
const auto& profileDefaults = _getJSONValue(profilesObject, DefaultSettingsKey);
const auto& profilesList = profilesObject.isArray() ? profilesObject : _getJSONValue(profilesObject, ProfilesListKey);
return JsonSettings{ std::move(root), colorSchemes, profileDefaults, profilesList };
}
// Just a common helper function between _parse and _parseFragment.
// Parses a profile and ensures it has a Guid if possible.
winrt::com_ptr<Profile> SettingsLoader::_parseProfile(const OriginTag origin, const winrt::hstring& source, const Json::Value& profileJson)
{
auto profile = Profile::FromJson(profileJson);
profile->Origin(origin);
// The Guid() generation below depends on the value of Source().
// --> Provide one if we got one.
if (!source.empty())
{
profile->Source(source);
}
// If none exists. the Guid() getter generates one from Name() and optionally Source().
// We want to ensure that every profile has a GUID no matter what, not just to
// cache the value, but also to make them consistently identifiable later on.
if (!profile->HasGuid() && profile->HasName())
{
profile->Guid(profile->Guid());
}
return profile;
}
// Adds a profile to the ParsedSettings instance. Takes ownership of the profile.
// It ensures no duplicate GUIDs are added to the ParsedSettings instance.
void SettingsLoader::_appendProfile(winrt::com_ptr<Profile>&& profile, ParsedSettings& settings)
@ -549,6 +579,28 @@ void SettingsLoader::_appendProfile(winrt::com_ptr<Profile>&& profile, ParsedSet
}
}
// If the given ParsedSettings instance contains a profile with the given profile's GUID,
// the profile is added as a parent. Otherwise a new child profile is created.
void SettingsLoader::_addParentProfile(const winrt::com_ptr<implementation::Profile>& profile, ParsedSettings& settings)
{
if (const auto [it, inserted] = settings.profilesByGuid.emplace(profile->Guid(), profile); !inserted)
{
// If inserted is false, we got a matching user profile with identical GUID.
// --> The generated profile is a parent of the existing user profile.
it->second->InsertParent(profile);
}
else
{
// If inserted is true, then this is a generated profile that doesn't exist in the user's settings.
// While emplace() has already created an appropriate entry in .profilesByGuid, we still need to
// add it to .profiles (which is basically a sorted list of .profilesByGuid's values).
//
// When a user modifies a profile they shouldn't modify the (static/constant)
// inbox profile of course. That's why we need to call CreateChild here.
settings.profiles.emplace_back(CreateChild(profile));
}
}
// As the name implies it executes a generator.
// Generated profiles are added to .inboxSettings. Used by GenerateProfiles().
void SettingsLoader::_executeGenerator(const IDynamicProfileGenerator& generator)

View File

@ -39,6 +39,83 @@ namespace winrt::Microsoft::Terminal::Settings::Model
return baseSettingsPath;
}
locked_hfile OpenFileReadSharedLocked(const std::filesystem::path& path)
{
wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr) };
THROW_LAST_ERROR_IF(!file);
// just lock the entire file
OVERLAPPED sOverlapped;
sOverlapped.Offset = 0;
sOverlapped.OffsetHigh = 0;
// Shared lock
THROW_LAST_ERROR_IF(!LockFileEx(file.get(),
0, // lock shared, wait to return until lock is obtained
0, // reserved, does nothing
INT_MAX, // lock INT_MAX bytes
0, // higher-order bytes, if our state file is greater than 2GB I guess this will be a problem
&sOverlapped));
return { std::move(file), sOverlapped };
}
locked_hfile OpenFileRWExclusiveLocked(const std::filesystem::path& path)
{
wil::unique_hfile file{ CreateFileW(path.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) };
THROW_LAST_ERROR_IF(!file);
// just lock the entire file
OVERLAPPED sOverlapped;
sOverlapped.Offset = 0;
sOverlapped.OffsetHigh = 0;
// Shared lock
THROW_LAST_ERROR_IF(!LockFileEx(file.get(),
LOCKFILE_EXCLUSIVE_LOCK, // lock exclusive, wait to return until lock is obtained
0, // reserved, does nothing
INT_MAX, // lock INT_MAX bytes
0, // higher-order bytes, if our state file is greater than 2GB I guess this will be a problem
&sOverlapped));
return { std::move(file), sOverlapped };
}
std::string ReadUTF8FileLocked(const locked_hfile& file)
{
const auto fileSize = GetFileSize(file.get(), nullptr);
THROW_LAST_ERROR_IF(fileSize == INVALID_FILE_SIZE);
// By making our buffer just slightly larger we can detect if
// the file size changed and we've failed to read the full file.
std::string buffer(static_cast<size_t>(fileSize) + 1, '\0');
DWORD bytesRead = 0;
THROW_IF_WIN32_BOOL_FALSE(ReadFile(file.get(), buffer.data(), gsl::narrow<DWORD>(buffer.size()), &bytesRead, nullptr));
// As mentioned before our buffer was allocated oversized.
buffer.resize(bytesRead);
if (til::starts_with(buffer, Utf8Bom))
{
// Yeah this memmove()s the entire content.
// But I don't really want to deal with UTF8 BOMs any more than necessary,
// as basically not a single editor writes a BOM for UTF8.
buffer.erase(0, Utf8Bom.size());
}
return buffer;
}
void WriteUTF8FileLocked(const locked_hfile& file, const std::string_view& content)
{
// truncate the file because we want to overwrite it
SetFilePointer(file.get(), 0, nullptr, FILE_BEGIN);
THROW_IF_WIN32_BOOL_FALSE(SetEndOfFile(file.get()));
const auto fileSize = gsl::narrow<DWORD>(content.size());
DWORD bytesWritten = 0;
THROW_IF_WIN32_BOOL_FALSE(WriteFile(file.get(), content.data(), fileSize, &bytesWritten, nullptr));
if (bytesWritten != fileSize)
{
THROW_WIN32_MSG(ERROR_WRITE_FAULT, "failed to write whole file");
}
}
// Tries to read a file somewhat atomically without locking it.
// Strips the UTF8 BOM if it exists.
std::string ReadUTF8File(const std::filesystem::path& path)

View File

@ -1,9 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
namespace winrt::Microsoft::Terminal::Settings::Model
{
// I couldn't find a wil helper for this so I made it myself
class locked_hfile
{
public:
wil::unique_hfile file;
OVERLAPPED lockedRegion;
~locked_hfile()
{
if (file)
{
// Need to unlock the file before it is closed
UnlockFileEx(file.get(), 0, INT_MAX, 0, &lockedRegion);
}
}
HANDLE get() const noexcept
{
return file.get();
}
};
std::filesystem::path GetBaseSettingsPath();
locked_hfile OpenFileReadSharedLocked(const std::filesystem::path& path);
locked_hfile OpenFileRWExclusiveLocked(const std::filesystem::path& path);
std::string ReadUTF8FileLocked(const locked_hfile& file);
void WriteUTF8FileLocked(const locked_hfile& file, const std::string_view& content);
std::string ReadUTF8File(const std::filesystem::path& path);
std::optional<std::string> ReadUTF8FileIfExists(const std::filesystem::path& path);
void WriteUTF8File(const std::filesystem::path& path, const std::string_view& content);

View File

@ -51,6 +51,7 @@ static constexpr std::string_view TrimBlockSelectionKey{ "trimBlockSelection" };
static constexpr std::string_view AlwaysShowNotificationIconKey{ "alwaysShowNotificationIcon" };
static constexpr std::string_view MinimizeToNotificationAreaKey{ "minimizeToNotificationArea" };
static constexpr std::string_view DisabledProfileSourcesKey{ "disabledProfileSources" };
static constexpr std::string_view ShowAdminShieldKey{ "showAdminShield" };
static constexpr std::string_view DebugFeaturesKey{ "debugFeatures" };
@ -121,6 +122,8 @@ winrt::com_ptr<GlobalAppSettings> GlobalAppSettings::Copy() const
globals->_MinimizeToNotificationArea = _MinimizeToNotificationArea;
globals->_AlwaysShowNotificationIcon = _AlwaysShowNotificationIcon;
globals->_DisabledProfileSources = _DisabledProfileSources;
globals->_ShowAdminShield = _ShowAdminShield;
globals->_UnparsedDefaultProfile = _UnparsedDefaultProfile;
globals->_defaultProfile = _defaultProfile;
@ -227,6 +230,8 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
JsonUtils::GetValueForKey(json, AlwaysShowNotificationIconKey, _AlwaysShowNotificationIcon);
JsonUtils::GetValueForKey(json, DisabledProfileSourcesKey, _DisabledProfileSources);
JsonUtils::GetValueForKey(json, ShowAdminShieldKey, _ShowAdminShield);
static constexpr std::array bindingsKeys{ LegacyKeybindingsKey, ActionsKey };
for (const auto& jsonKey : bindingsKeys)
{
@ -324,6 +329,7 @@ Json::Value GlobalAppSettings::ToJson() const
JsonUtils::SetValueForKey(json, MinimizeToNotificationAreaKey, _MinimizeToNotificationArea);
JsonUtils::SetValueForKey(json, AlwaysShowNotificationIconKey, _AlwaysShowNotificationIcon);
JsonUtils::SetValueForKey(json, DisabledProfileSourcesKey, _DisabledProfileSources);
JsonUtils::SetValueForKey(json, ShowAdminShieldKey, _ShowAdminShield);
// clang-format on
json[JsonKey(ActionsKey)] = _actionMap->ToJson();

View File

@ -99,6 +99,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
INHERITABLE_SETTING(Model::GlobalAppSettings, bool, AlwaysShowNotificationIcon, false);
INHERITABLE_SETTING(Model::GlobalAppSettings, winrt::Windows::Foundation::Collections::IVector<winrt::hstring>, DisabledProfileSources, nullptr);
INHERITABLE_SETTING(Model::GlobalAppSettings, hstring, UnparsedDefaultProfile, L"");
INHERITABLE_SETTING(Model::GlobalAppSettings, bool, ShowAdminShield, true);
private:
#ifdef NDEBUG

View File

@ -84,6 +84,7 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_SETTING(Boolean, MinimizeToNotificationArea);
INHERITABLE_SETTING(Boolean, AlwaysShowNotificationIcon);
INHERITABLE_SETTING(IVector<String>, DisabledProfileSources);
INHERITABLE_SETTING(Boolean, ShowAdminShield);
Windows.Foundation.Collections.IMapView<String, ColorScheme> ColorSchemes();
void AddColorScheme(ColorScheme scheme);

View File

@ -101,7 +101,7 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
{
public:
DeserializationError(const Json::Value& value) :
runtime_error(std::string("failed to deserialize ") + (value.isNull() ? "" : value.asCString())),
runtime_error(std::string("failed to deserialize ") + (value.isNull() ? "" : value.asString())),
jsonValue{ value } {}
void SetKey(std::string_view newKey)

View File

@ -14,7 +14,8 @@
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="BaseVisualStudioGenerator.h" />
<ClInclude Include="VcDevCmdGenerator.h" />
<ClInclude Include="VisualStudioGenerator.h" />
<ClInclude Include="DefaultTerminal.h">
<DependentUpon>DefaultTerminal.idl</DependentUpon>
</ClInclude>
@ -84,7 +85,8 @@
</ItemGroup>
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
<ClCompile Include="BaseVisualStudioGenerator.cpp" />
<ClCompile Include="VcDevCmdGenerator.cpp" />
<ClCompile Include="VisualStudioGenerator.cpp" />
<ClCompile Include="DefaultTerminal.cpp">
<DependentUpon>DefaultTerminal.idl</DependentUpon>
</ClCompile>
@ -241,7 +243,6 @@
</ItemDefinitionGroup>
<!-- ========================= Globals ======================== -->
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.post.props" />
<Import Project="..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets" Condition="Exists('..\..\..\packages\Microsoft.UI.Xaml.2.7.0-prerelease.210913003\build\native\Microsoft.UI.Xaml.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
@ -266,4 +267,4 @@
</Target>
<Import Project="$(SolutionDir)build\rules\CollectWildcardResources.targets" />
<Import Project="..\..\..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets" Condition="Exists('..\..\..\packages\Microsoft.VisualStudio.Setup.Configuration.Native.2.3.2262\build\native\Microsoft.VisualStudio.Setup.Configuration.Native.targets')" />
</Project>
</Project>

View File

@ -34,7 +34,7 @@
<ClCompile Include="IconPathConverter.cpp" />
<ClCompile Include="DefaultTerminal.cpp" />
<ClCompile Include="FileUtils.cpp" />
<ClCompile Include="BaseVisualStudioGenerator.cpp">
<ClCompile Include="VisualStudioGenerator.cpp">
<Filter>profileGeneration</Filter>
</ClCompile>
<ClCompile Include="VsDevCmdGenerator.cpp">
@ -46,6 +46,9 @@
<ClCompile Include="VsSetupConfiguration.cpp">
<Filter>profileGeneration</Filter>
</ClCompile>
<ClCompile Include="VcDevCmdGenerator.cpp">
<Filter>profileGeneration</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@ -80,7 +83,7 @@
<ClInclude Include="DefaultTerminal.h" />
<ClInclude Include="FileUtils.h" />
<ClInclude Include="HashUtils.h" />
<ClInclude Include="BaseVisualStudioGenerator.h">
<ClInclude Include="VisualStudioGenerator.h">
<Filter>profileGeneration</Filter>
</ClInclude>
<ClInclude Include="VsDevCmdGenerator.h">
@ -92,6 +95,9 @@
<ClInclude Include="VsSetupConfiguration.h">
<Filter>profileGeneration</Filter>
</ClInclude>
<ClInclude Include="VcDevCmdGenerator.h">
<Filter>profileGeneration</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Midl Include="ActionArgs.idl" />
@ -123,4 +129,4 @@
<UniqueIdentifier>{81a6314f-aa5b-4533-a499-13bc3a5c4af0}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>
</Project>

View File

@ -258,6 +258,12 @@
<data name="MoveFocusFirstPane" xml:space="preserve">
<value>Move focus to the first pane</value>
</data>
<data name="MoveFocusParentPane" xml:space="preserve">
<value>Move focus to the parent pane</value>
</data>
<data name="MoveFocusChildPane" xml:space="preserve">
<value>Move focus to the child pane</value>
</data>
<data name="SwapPaneCommandKey" xml:space="preserve">
<value>Swap pane</value>
</data>
@ -427,7 +433,7 @@
<value>Break into the debugger</value>
</data>
<data name="OpenSettingsUICommandKey" xml:space="preserve">
<value>Open Settings...</value>
<value>Open settings...</value>
</data>
<data name="RenameWindowCommandKey" xml:space="preserve">
<value>Rename window to "{0}"</value>
@ -450,15 +456,15 @@
<comment>{0} will be replaced with a user-specified number</comment>
</data>
<data name="ClearAllCommandKey" xml:space="preserve">
<value>Clear Buffer</value>
<value>Clear buffer</value>
<comment>A command to clear the entirety of the Terminal output buffer</comment>
</data>
<data name="ClearViewportCommandKey" xml:space="preserve">
<value>Clear Viewport</value>
<value>Clear viewport</value>
<comment>A command to clear the active viewport of the Terminal</comment>
</data>
<data name="ClearScrollbackCommandKey" xml:space="preserve">
<value>Clear Scrollback</value>
<value>Clear scrollback</value>
<comment>A command to clear the part of the buffer above the viewport</comment>
</data>
<data name="InboxWindowsConsoleAuthor" xml:space="preserve">

View File

@ -202,15 +202,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
_IntenseIsBold = WI_IsFlagSet(appearance.IntenseTextStyle(), Microsoft::Terminal::Settings::Model::IntenseStyle::Bold);
_IntenseIsBright = WI_IsFlagSet(appearance.IntenseTextStyle(), Microsoft::Terminal::Settings::Model::IntenseStyle::Bright);
// If the user set an opacity, then just use that. Otherwise, change the
// default value based off of whether useAcrylic was set or not. If they
// want acrylic, then default to 50%. Otherwise, default to 100% (fully
// opaque)
_Opacity = appearance.HasOpacity() ?
appearance.Opacity() :
UseAcrylic() ?
.5 :
1.0;
_Opacity = appearance.Opacity();
}
// Method Description:

View File

@ -373,7 +373,7 @@ struct IntAsFloatPercentConversionTrait : ::Microsoft::Terminal::Settings::Model
// Possible FocusDirection values
JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::FocusDirection)
{
JSON_MAPPINGS(8) = {
JSON_MAPPINGS(10) = {
pair_type{ "left", ValueType::Left },
pair_type{ "right", ValueType::Right },
pair_type{ "up", ValueType::Up },
@ -382,6 +382,8 @@ JSON_ENUM_MAPPER(::winrt::Microsoft::Terminal::Settings::Model::FocusDirection)
pair_type{ "previousInOrder", ValueType::PreviousInOrder },
pair_type{ "nextInOrder", ValueType::NextInOrder },
pair_type{ "first", ValueType::First },
pair_type{ "parent", ValueType::Parent },
pair_type{ "child", ValueType::Child },
};
};

View File

@ -0,0 +1,94 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "DynamicProfileUtils.h"
#include "VcDevCmdGenerator.h"
using namespace winrt::Microsoft::Terminal::Settings::Model;
void VcDevCmdGenerator::GenerateProfiles(const VsSetupConfiguration::VsSetupInstance& instance, bool hidden, std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
{
try
{
const std::filesystem::path root{ GetVcCmdScriptDirectory(instance) };
if (!std::filesystem::exists(root))
{
return;
}
// x64 environments only installed on 64-bit machines.
#ifdef _WIN64
const auto vcvars64 = root / L"vcvars64.bat";
if (std::filesystem::exists(vcvars64))
{
auto profile = CreateProfile(instance, L"x64", vcvars64, hidden);
profiles.emplace_back(std::move(profile));
// Only the VC environment for the matching architecture should be shown by default.
hidden = true;
}
const auto vcvarsamd64_x86 = root / L"vcvarsamd64_x86.bat";
if (std::filesystem::exists(vcvarsamd64_x86))
{
auto profile = CreateProfile(instance, L"x64_x86", vcvarsamd64_x86, true);
profiles.emplace_back(std::move(profile));
}
const auto vcvarsx86_amd64 = root / L"vcvarsx86_amd64.bat";
if (std::filesystem::exists(vcvarsx86_amd64))
{
auto profile = CreateProfile(instance, L"x86_x64", vcvarsx86_amd64, true);
profiles.emplace_back(std::move(profile));
}
#endif // _WIN64
const auto vcvars32 = root / L"vcvars32.bat";
if (std::filesystem::exists(vcvars32))
{
auto profile = CreateProfile(instance, L"x86", vcvars32, hidden);
profiles.emplace_back(std::move(profile));
}
}
CATCH_LOG();
}
winrt::com_ptr<implementation::Profile> VcDevCmdGenerator::CreateProfile(const VsSetupConfiguration::VsSetupInstance& instance, const std::wstring_view& prefix, const std::filesystem::path& path, bool hidden) const
{
const auto seed = GetProfileGuidSeed(instance, path);
const winrt::guid profileGuid{ ::Microsoft::Console::Utils::CreateV5Uuid(TERMINAL_PROFILE_NAMESPACE_GUID, gsl::as_bytes(gsl::make_span(seed))) };
auto profile = winrt::make_self<implementation::Profile>(profileGuid);
profile->Name(winrt::hstring{ GetProfileName(instance, prefix) });
profile->Commandline(winrt::hstring{ GetProfileCommandLine(path) });
profile->StartingDirectory(winrt::hstring{ instance.GetInstallationPath() });
profile->Icon(winrt::hstring{ GetProfileIconPath() });
profile->Hidden(hidden);
return profile;
}
std::wstring VcDevCmdGenerator::GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance& instance, const std::filesystem::path& path) const
{
return L"VsDevCmd" + instance.GetInstanceId() + path.native();
}
std::wstring VcDevCmdGenerator::GetProfileName(const VsSetupConfiguration::VsSetupInstance& instance, const std::wstring_view& prefix) const
{
std::wstring name{ prefix + L" Native Tools Command Prompt for VS " };
name.append(instance.GetProfileNameSuffix());
return name;
}
std::wstring VcDevCmdGenerator::GetProfileCommandLine(const std::filesystem::path& path) const
{
std::wstring commandLine{ L"cmd.exe /k \"" + path.native() + L"\"" };
return commandLine;
}
std::wstring VcDevCmdGenerator::GetVcCmdScriptDirectory(const VsSetupConfiguration::VsSetupInstance& instance) const
{
return instance.ResolvePath(L"VC\\Auxiliary\\Build\\");
}

View File

@ -0,0 +1,40 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- VcDevCmdGenerator
Abstract:
- Dynamic profile generator for Visual C++ development environments.
Author(s):
- Heath Stewart - September 2021
--*/
#pragma once
#include "VisualStudioGenerator.h"
#include "VsSetupConfiguration.h"
namespace winrt::Microsoft::Terminal::Settings::Model
{
class VcDevCmdGenerator final : public VisualStudioGenerator::IVisualStudioProfileGenerator
{
public:
void GenerateProfiles(const VsSetupConfiguration::VsSetupInstance& instance, bool hidden, std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
private:
winrt::com_ptr<implementation::Profile> CreateProfile(const VsSetupConfiguration::VsSetupInstance& instance, const std::wstring_view& prefix, const std::filesystem::path& path, bool hidden) const;
std::wstring GetProfileIconPath() const
{
return L"ms-appx:///ProfileIcons/{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.png";
}
std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance& instance, const std::filesystem::path& path) const;
std::wstring GetProfileName(const VsSetupConfiguration::VsSetupInstance& instance, const std::wstring_view& prefix) const;
std::wstring GetProfileCommandLine(const std::filesystem::path& path) const;
std::wstring GetVcCmdScriptDirectory(const VsSetupConfiguration::VsSetupInstance& instance) const;
};
};

View File

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "DynamicProfileUtils.h"
#include "VisualStudioGenerator.h"
#include "VcDevCmdGenerator.h"
#include "VsDevCmdGenerator.h"
#include "VsDevShellGenerator.h"
using namespace winrt::Microsoft::Terminal::Settings::Model;
std::wstring_view VisualStudioGenerator::GetNamespace() const noexcept
{
return std::wstring_view{ L"Windows.Terminal.VisualStudio" };
}
void VisualStudioGenerator::GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
{
const auto instances = VsSetupConfiguration::QueryInstances();
VsDevCmdGenerator devCmdGenerator;
VcDevCmdGenerator vcCmdGenerator;
VsDevShellGenerator devShellGenerator;
// Instances are ordered from latest to oldest. Hide all but the profiles for the latest instance.
bool hidden = false;
for (auto const& instance : instances)
{
devCmdGenerator.GenerateProfiles(instance, hidden, profiles);
vcCmdGenerator.GenerateProfiles(instance, hidden, profiles);
devShellGenerator.GenerateProfiles(instance, hidden, profiles);
hidden = true;
}
}

View File

@ -0,0 +1,38 @@
/*++
Copyright (c) Microsoft Corporation
Licensed under the MIT license.
Module Name:
- VisualStudioGenerator
Abstract:
- Generator for Visual Studio shell profiles. Actual profile generation is delegated
to separate classes that encapsulate different logic for cmd- and powershell-based shells,
as well as VC startup scripts specific to the current processor architecture.
Author(s):
- Charles Willis - October 2020
- Heath Stewart - September 2021
--*/
#pragma once
#include "IDynamicProfileGenerator.h"
#include "VsSetupConfiguration.h"
namespace winrt::Microsoft::Terminal::Settings::Model
{
class VisualStudioGenerator : public IDynamicProfileGenerator
{
public:
std::wstring_view GetNamespace() const noexcept override;
void GenerateProfiles(std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
class IVisualStudioProfileGenerator
{
public:
virtual void GenerateProfiles(const VsSetupConfiguration::VsSetupInstance& instance, bool hidden, std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const = 0;
};
};
};

View File

@ -2,10 +2,33 @@
// Licensed under the MIT license.
#include "pch.h"
#include "DynamicProfileUtils.h"
#include "VsDevCmdGenerator.h"
using namespace winrt::Microsoft::Terminal::Settings::Model;
void VsDevCmdGenerator::GenerateProfiles(const VsSetupConfiguration::VsSetupInstance& instance, bool hidden, std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
{
try
{
if (!IsInstanceValid(instance))
{
return;
}
const auto seed = GetProfileGuidSeed(instance);
const winrt::guid profileGuid{ ::Microsoft::Console::Utils::CreateV5Uuid(TERMINAL_PROFILE_NAMESPACE_GUID, gsl::as_bytes(gsl::make_span(seed))) };
auto profile = winrt::make_self<implementation::Profile>(profileGuid);
profile->Name(winrt::hstring{ GetProfileName(instance) });
profile->Commandline(winrt::hstring{ GetProfileCommandLine(instance) });
profile->StartingDirectory(winrt::hstring{ instance.GetInstallationPath() });
profile->Icon(winrt::hstring{ GetProfileIconPath() });
profile->Hidden(hidden);
profiles.emplace_back(std::move(profile));
}
CATCH_LOG();
}
std::wstring VsDevCmdGenerator::GetProfileName(const VsSetupConfiguration::VsSetupInstance& instance) const
{
std::wstring name{ L"Developer Command Prompt for VS " };
@ -15,7 +38,12 @@ std::wstring VsDevCmdGenerator::GetProfileName(const VsSetupConfiguration::VsSet
std::wstring VsDevCmdGenerator::GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance& instance) const
{
std::wstring commandLine{ L"cmd.exe /k \"" + instance.GetDevCmdScriptPath() + L"\"" };
std::wstring commandLine{ L"cmd.exe /k \"" + GetDevCmdScriptPath(instance) + L"\"" };
return commandLine;
}
std::wstring VsDevCmdGenerator::GetDevCmdScriptPath(const VsSetupConfiguration::VsSetupInstance& instance) const
{
return instance.ResolvePath(L"Common7\\Tools\\VsDevCmd.bat");
}

View File

@ -10,23 +10,23 @@ Abstract:
Author(s):
- Charles Willis - October 2020
- Heath Stewart - September 2021
--*/
#pragma once
#include "BaseVisualStudioGenerator.h"
#include "VisualStudioGenerator.h"
#include "VsSetupConfiguration.h"
namespace winrt::Microsoft::Terminal::Settings::Model
{
class VsDevCmdGenerator final : public BaseVisualStudioGenerator
class VsDevCmdGenerator final : public VisualStudioGenerator::IVisualStudioProfileGenerator
{
public:
std::wstring_view GetNamespace() const noexcept override
{
return std::wstring_view{ L"Windows.Terminal.VisualStudio.CommandPrompt" };
}
void GenerateProfiles(const VsSetupConfiguration::VsSetupInstance& instance, bool hidden, std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance&) const override
private:
bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance&) const
{
// We only support version of VS from 15.0.
// Per heaths: The [ISetupConfiguration] COM server only supports Visual Studio 15.0 and newer anyway.
@ -34,17 +34,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model
return true;
}
std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance& instance) const override
std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance& instance) const
{
return L"VsDevCmd" + instance.GetInstanceId();
}
std::wstring GetProfileIconPath() const override
std::wstring GetProfileIconPath() const
{
return L"ms-appx:///ProfileIcons/{0caa0dad-35be-5f56-a8ff-afceeeaa6101}.png";
}
std::wstring GetProfileName(const VsSetupConfiguration::VsSetupInstance& instance) const override;
std::wstring GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance& instance) const override;
std::wstring GetProfileName(const VsSetupConfiguration::VsSetupInstance& instance) const;
std::wstring GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance& instance) const;
std::wstring GetDevCmdScriptPath(const VsSetupConfiguration::VsSetupInstance& instance) const;
};
};

View File

@ -2,11 +2,34 @@
// Licensed under the MIT license.
#include "pch.h"
#include "DynamicProfileUtils.h"
#include "VsDevShellGenerator.h"
#include "VsSetupConfiguration.h"
using namespace winrt::Microsoft::Terminal::Settings::Model;
void VsDevShellGenerator::GenerateProfiles(const VsSetupConfiguration::VsSetupInstance& instance, bool hidden, std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const
{
try
{
if (!IsInstanceValid(instance))
{
return;
}
const auto seed = GetProfileGuidSeed(instance);
const winrt::guid profileGuid{ ::Microsoft::Console::Utils::CreateV5Uuid(TERMINAL_PROFILE_NAMESPACE_GUID, gsl::as_bytes(gsl::make_span(seed))) };
auto profile = winrt::make_self<implementation::Profile>(profileGuid);
profile->Name(winrt::hstring{ GetProfileName(instance) });
profile->Commandline(winrt::hstring{ GetProfileCommandLine(instance) });
profile->StartingDirectory(winrt::hstring{ instance.GetInstallationPath() });
profile->Icon(winrt::hstring{ GetProfileIconPath() });
profile->Hidden(hidden);
profiles.emplace_back(std::move(profile));
}
CATCH_LOG();
}
std::wstring VsDevShellGenerator::GetProfileName(const VsSetupConfiguration::VsSetupInstance& instance) const
{
std::wstring name{ L"Developer PowerShell for VS " };
@ -20,9 +43,20 @@ std::wstring VsDevShellGenerator::GetProfileCommandLine(const VsSetupConfigurati
// The "SkipAutomaticLocation" parameter will prevent "Enter-VsDevShell" from automatically setting the shell path
// so the path in the profile will be used instead.
std::wstring commandLine{ L"powershell.exe -NoExit -Command \"& {" };
commandLine.append(L"Import-Module \"\"\"" + instance.GetDevShellModulePath() + L"\"\"\";");
commandLine.append(L"Import-Module \"\"\"" + GetDevShellModulePath(instance) + L"\"\"\";");
commandLine.append(L"Enter-VsDevShell " + instance.GetInstanceId() + L" -SkipAutomaticLocation");
commandLine.append(L"}\"");
return commandLine;
}
std::wstring VsDevShellGenerator::GetDevShellModulePath(const VsSetupConfiguration::VsSetupInstance& instance) const
{
// The path of Microsoft.VisualStudio.DevShell.dll changed in 16.3
if (instance.VersionInRange(L"[16.3,"))
{
return instance.ResolvePath(L"Common7\\Tools\\Microsoft.VisualStudio.DevShell.dll");
}
return instance.ResolvePath(L"Common7\\Tools\\vsdevshell\\Microsoft.VisualStudio.DevShell.dll");
}

View File

@ -10,38 +10,39 @@ Abstract:
Author(s):
- Charles Willis - October 2020
- Heath Stewart - September 2021
--*/
#pragma once
#include "BaseVisualStudioGenerator.h"
#include "VisualStudioGenerator.h"
#include "VsSetupConfiguration.h"
namespace winrt::Microsoft::Terminal::Settings::Model
{
class VsDevShellGenerator final : public BaseVisualStudioGenerator
class VsDevShellGenerator final : public VisualStudioGenerator::IVisualStudioProfileGenerator
{
public:
std::wstring_view GetNamespace() const noexcept override
{
return std::wstring_view{ L"Windows.Terminal.VisualStudio.Powershell" };
}
void GenerateProfiles(const VsSetupConfiguration::VsSetupInstance& instance, bool hidden, std::vector<winrt::com_ptr<implementation::Profile>>& profiles) const override;
bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance& instance) const override
private:
bool IsInstanceValid(const VsSetupConfiguration::VsSetupInstance& instance) const
{
return instance.VersionInRange(L"[16.2,)");
}
std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance& instance) const override
std::wstring GetProfileGuidSeed(const VsSetupConfiguration::VsSetupInstance& instance) const
{
return L"VsDevShell" + instance.GetInstanceId();
}
std::wstring GetProfileIconPath() const override
std::wstring GetProfileIconPath() const
{
return L"ms-appx:///ProfileIcons/{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.png";
}
std::wstring GetProfileName(const VsSetupConfiguration::VsSetupInstance& instance) const override;
std::wstring GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance& instance) const override;
std::wstring GetProfileName(const VsSetupConfiguration::VsSetupInstance& instance) const;
std::wstring GetProfileCommandLine(const VsSetupConfiguration::VsSetupInstance& instance) const;
std::wstring GetDevShellModulePath(const VsSetupConfiguration::VsSetupInstance& instance) const;
};
};

View File

@ -32,6 +32,19 @@ std::vector<VsSetupConfiguration::VsSetupInstance> VsSetupConfiguration::QueryIn
THROW_IF_FAILED(result);
// Sort instances based on version and install date from latest to oldest.
std::sort(instances.begin(), instances.end(), [](const VsSetupInstance& a, const VsSetupInstance& b) {
auto const aVersion = a.GetComparableVersion();
auto const bVersion = b.GetComparableVersion();
if (aVersion == bVersion)
{
return a.GetComparableInstallDate() > b.GetComparableInstallDate();
}
return aVersion > bVersion;
});
return instances;
}
@ -95,6 +108,13 @@ std::wstring VsSetupConfiguration::GetInstanceId(ISetupInstance* pInst)
return bstrInstanceId.get();
}
unsigned long long VsSetupConfiguration::GetInstallDate(ISetupInstance* pInst)
{
FILETIME ftInstallDate{ 0 };
THROW_IF_FAILED(pInst->GetInstallDate(&ftInstallDate));
return wil::filetime::to_int64(ftInstallDate);
}
std::wstring VsSetupConfiguration::GetStringProperty(ISetupPropertyStore* pProps, std::wstring_view name)
{
if (pProps == nullptr)

Some files were not shown because too many files have changed in this diff Show More